Save time-control settings in tourney file
[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;
866
867 void
868 Load(ChessProgramState *cps, int i)
869 {
870     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871     if(engineLine[0]) { // an engine was selected from the combo box
872         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
875         ParseArgsFromString(buf);
876         SwapEngines(i);
877         ReplaceEngine(cps, i);
878         return;
879     }
880     p = engineName;
881     while(q = strchr(p, SLASH)) p = q+1;
882     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883     if(engineDir[0] != NULLCHAR)
884         appData.directory[i] = engineDir;
885     else if(p != engineName) { // derive directory from engine path, when not given
886         p[-1] = 0;
887         appData.directory[i] = strdup(engineName);
888         p[-1] = SLASH;
889     } else appData.directory[i] = ".";
890     if(params[0]) {
891         snprintf(command, MSG_SIZ, "%s %s", p, params);
892         p = command;
893     }
894     appData.chessProgram[i] = strdup(p);
895     appData.isUCI[i] = isUCI;
896     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897     appData.hasOwnBookUCI[i] = hasBook;
898     if(addToList) {
899         int len;
900         q = firstChessProgramNames;
901         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
902         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
903                         v1 ? " -firstProtocolVersion 1" : "",
904                         hasBook ? "" : " -fNoOwnBookUCI",
905                         isUCI ? " -fUCI" : "",
906                         storeVariant ? " -variant " : "",
907                         storeVariant ? VariantName(gameInfo.variant) : "");
908         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
909         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
910         if(q)   free(q);
911     }
912     ReplaceEngine(cps, i);
913 }
914
915 void
916 InitTimeControls()
917 {
918     int matched, min, sec;
919     /*
920      * Parse timeControl resource
921      */
922     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
923                           appData.movesPerSession)) {
924         char buf[MSG_SIZ];
925         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
926         DisplayFatalError(buf, 0, 2);
927     }
928
929     /*
930      * Parse searchTime resource
931      */
932     if (*appData.searchTime != NULLCHAR) {
933         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
934         if (matched == 1) {
935             searchTime = min * 60;
936         } else if (matched == 2) {
937             searchTime = min * 60 + sec;
938         } else {
939             char buf[MSG_SIZ];
940             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
941             DisplayFatalError(buf, 0, 2);
942         }
943     }
944 }
945
946 void
947 InitBackEnd1()
948 {
949
950     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
951     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
952
953     GetTimeMark(&programStartTime);
954     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
955     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
956
957     ClearProgramStats();
958     programStats.ok_to_send = 1;
959     programStats.seen_stat = 0;
960
961     /*
962      * Initialize game list
963      */
964     ListNew(&gameList);
965
966
967     /*
968      * Internet chess server status
969      */
970     if (appData.icsActive) {
971         appData.matchMode = FALSE;
972         appData.matchGames = 0;
973 #if ZIPPY
974         appData.noChessProgram = !appData.zippyPlay;
975 #else
976         appData.zippyPlay = FALSE;
977         appData.zippyTalk = FALSE;
978         appData.noChessProgram = TRUE;
979 #endif
980         if (*appData.icsHelper != NULLCHAR) {
981             appData.useTelnet = TRUE;
982             appData.telnetProgram = appData.icsHelper;
983         }
984     } else {
985         appData.zippyTalk = appData.zippyPlay = FALSE;
986     }
987
988     /* [AS] Initialize pv info list [HGM] and game state */
989     {
990         int i, j;
991
992         for( i=0; i<=framePtr; i++ ) {
993             pvInfoList[i].depth = -1;
994             boards[i][EP_STATUS] = EP_NONE;
995             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
996         }
997     }
998
999     InitTimeControls();
1000
1001     /* [AS] Adjudication threshold */
1002     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1003
1004     InitEngine(&first, 0);
1005     InitEngine(&second, 1);
1006     CommonEngineInit();
1007
1008     if (appData.icsActive) {
1009         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1010     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1011         appData.clockMode = FALSE;
1012         first.sendTime = second.sendTime = 0;
1013     }
1014
1015 #if ZIPPY
1016     /* Override some settings from environment variables, for backward
1017        compatibility.  Unfortunately it's not feasible to have the env
1018        vars just set defaults, at least in xboard.  Ugh.
1019     */
1020     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1021       ZippyInit();
1022     }
1023 #endif
1024
1025     if (!appData.icsActive) {
1026       char buf[MSG_SIZ];
1027       int len;
1028
1029       /* Check for variants that are supported only in ICS mode,
1030          or not at all.  Some that are accepted here nevertheless
1031          have bugs; see comments below.
1032       */
1033       VariantClass variant = StringToVariant(appData.variant);
1034       switch (variant) {
1035       case VariantBughouse:     /* need four players and two boards */
1036       case VariantKriegspiel:   /* need to hide pieces and move details */
1037         /* case VariantFischeRandom: (Fabien: moved below) */
1038         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1039         if( (len > MSG_SIZ) && appData.debugMode )
1040           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1041
1042         DisplayFatalError(buf, 0, 2);
1043         return;
1044
1045       case VariantUnknown:
1046       case VariantLoadable:
1047       case Variant29:
1048       case Variant30:
1049       case Variant31:
1050       case Variant32:
1051       case Variant33:
1052       case Variant34:
1053       case Variant35:
1054       case Variant36:
1055       default:
1056         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1057         if( (len > MSG_SIZ) && appData.debugMode )
1058           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1059
1060         DisplayFatalError(buf, 0, 2);
1061         return;
1062
1063       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1064       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1065       case VariantGothic:     /* [HGM] should work */
1066       case VariantCapablanca: /* [HGM] should work */
1067       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1068       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1069       case VariantKnightmate: /* [HGM] should work */
1070       case VariantCylinder:   /* [HGM] untested */
1071       case VariantFalcon:     /* [HGM] untested */
1072       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1073                                  offboard interposition not understood */
1074       case VariantNormal:     /* definitely works! */
1075       case VariantWildCastle: /* pieces not automatically shuffled */
1076       case VariantNoCastle:   /* pieces not automatically shuffled */
1077       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1078       case VariantLosers:     /* should work except for win condition,
1079                                  and doesn't know captures are mandatory */
1080       case VariantSuicide:    /* should work except for win condition,
1081                                  and doesn't know captures are mandatory */
1082       case VariantGiveaway:   /* should work except for win condition,
1083                                  and doesn't know captures are mandatory */
1084       case VariantTwoKings:   /* should work */
1085       case VariantAtomic:     /* should work except for win condition */
1086       case Variant3Check:     /* should work except for win condition */
1087       case VariantShatranj:   /* should work except for all win conditions */
1088       case VariantMakruk:     /* should work except for daw countdown */
1089       case VariantBerolina:   /* might work if TestLegality is off */
1090       case VariantCapaRandom: /* should work */
1091       case VariantJanus:      /* should work */
1092       case VariantSuper:      /* experimental */
1093       case VariantGreat:      /* experimental, requires legality testing to be off */
1094       case VariantSChess:     /* S-Chess, should work */
1095       case VariantSpartan:    /* should work */
1096         break;
1097       }
1098     }
1099
1100 }
1101
1102 int NextIntegerFromString( char ** str, long * value )
1103 {
1104     int result = -1;
1105     char * s = *str;
1106
1107     while( *s == ' ' || *s == '\t' ) {
1108         s++;
1109     }
1110
1111     *value = 0;
1112
1113     if( *s >= '0' && *s <= '9' ) {
1114         while( *s >= '0' && *s <= '9' ) {
1115             *value = *value * 10 + (*s - '0');
1116             s++;
1117         }
1118
1119         result = 0;
1120     }
1121
1122     *str = s;
1123
1124     return result;
1125 }
1126
1127 int NextTimeControlFromString( char ** str, long * value )
1128 {
1129     long temp;
1130     int result = NextIntegerFromString( str, &temp );
1131
1132     if( result == 0 ) {
1133         *value = temp * 60; /* Minutes */
1134         if( **str == ':' ) {
1135             (*str)++;
1136             result = NextIntegerFromString( str, &temp );
1137             *value += temp; /* Seconds */
1138         }
1139     }
1140
1141     return result;
1142 }
1143
1144 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1145 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1146     int result = -1, type = 0; long temp, temp2;
1147
1148     if(**str != ':') return -1; // old params remain in force!
1149     (*str)++;
1150     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1151     if( NextIntegerFromString( str, &temp ) ) return -1;
1152     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1153
1154     if(**str != '/') {
1155         /* time only: incremental or sudden-death time control */
1156         if(**str == '+') { /* increment follows; read it */
1157             (*str)++;
1158             if(**str == '!') type = *(*str)++; // Bronstein TC
1159             if(result = NextIntegerFromString( str, &temp2)) return -1;
1160             *inc = temp2 * 1000;
1161             if(**str == '.') { // read fraction of increment
1162                 char *start = ++(*str);
1163                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1164                 temp2 *= 1000;
1165                 while(start++ < *str) temp2 /= 10;
1166                 *inc += temp2;
1167             }
1168         } else *inc = 0;
1169         *moves = 0; *tc = temp * 1000; *incType = type;
1170         return 0;
1171     }
1172
1173     (*str)++; /* classical time control */
1174     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1175
1176     if(result == 0) {
1177         *moves = temp;
1178         *tc    = temp2 * 1000;
1179         *inc   = 0;
1180         *incType = type;
1181     }
1182     return result;
1183 }
1184
1185 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1186 {   /* [HGM] get time to add from the multi-session time-control string */
1187     int incType, moves=1; /* kludge to force reading of first session */
1188     long time, increment;
1189     char *s = tcString;
1190
1191     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1192     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1193     do {
1194         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1195         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1196         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1197         if(movenr == -1) return time;    /* last move before new session     */
1198         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1199         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1200         if(!moves) return increment;     /* current session is incremental   */
1201         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1202     } while(movenr >= -1);               /* try again for next session       */
1203
1204     return 0; // no new time quota on this move
1205 }
1206
1207 int
1208 ParseTimeControl(tc, ti, mps)
1209      char *tc;
1210      float ti;
1211      int mps;
1212 {
1213   long tc1;
1214   long tc2;
1215   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1216   int min, sec=0;
1217
1218   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1219   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1220       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1221   if(ti > 0) {
1222
1223     if(mps)
1224       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1225     else 
1226       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1227   } else {
1228     if(mps)
1229       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1230     else 
1231       snprintf(buf, MSG_SIZ, ":%s", mytc);
1232   }
1233   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1234   
1235   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1236     return FALSE;
1237   }
1238
1239   if( *tc == '/' ) {
1240     /* Parse second time control */
1241     tc++;
1242
1243     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1244       return FALSE;
1245     }
1246
1247     if( tc2 == 0 ) {
1248       return FALSE;
1249     }
1250
1251     timeControl_2 = tc2 * 1000;
1252   }
1253   else {
1254     timeControl_2 = 0;
1255   }
1256
1257   if( tc1 == 0 ) {
1258     return FALSE;
1259   }
1260
1261   timeControl = tc1 * 1000;
1262
1263   if (ti >= 0) {
1264     timeIncrement = ti * 1000;  /* convert to ms */
1265     movesPerSession = 0;
1266   } else {
1267     timeIncrement = 0;
1268     movesPerSession = mps;
1269   }
1270   return TRUE;
1271 }
1272
1273 void
1274 InitBackEnd2()
1275 {
1276     if (appData.debugMode) {
1277         fprintf(debugFP, "%s\n", programVersion);
1278     }
1279
1280     set_cont_sequence(appData.wrapContSeq);
1281     if (appData.matchGames > 0) {
1282         appData.matchMode = TRUE;
1283     } else if (appData.matchMode) {
1284         appData.matchGames = 1;
1285     }
1286     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1287         appData.matchGames = appData.sameColorGames;
1288     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1289         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1290         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1291     }
1292     Reset(TRUE, FALSE);
1293     if (appData.noChessProgram || first.protocolVersion == 1) {
1294       InitBackEnd3();
1295     } else {
1296       /* kludge: allow timeout for initial "feature" commands */
1297       FreezeUI();
1298       DisplayMessage("", _("Starting chess program"));
1299       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1300     }
1301 }
1302
1303 int
1304 CalculateIndex(int index, int gameNr)
1305 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1306     int res;
1307     if(index > 0) return index; // fixed nmber
1308     if(index == 0) return 1;
1309     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1310     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1311     return res;
1312 }
1313
1314 int
1315 LoadGameOrPosition(int gameNr)
1316 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1317     if (*appData.loadGameFile != NULLCHAR) {
1318         if (!LoadGameFromFile(appData.loadGameFile,
1319                 CalculateIndex(appData.loadGameIndex, gameNr),
1320                               appData.loadGameFile, FALSE)) {
1321             DisplayFatalError(_("Bad game file"), 0, 1);
1322             return 0;
1323         }
1324     } else if (*appData.loadPositionFile != NULLCHAR) {
1325         if (!LoadPositionFromFile(appData.loadPositionFile,
1326                 CalculateIndex(appData.loadPositionIndex, gameNr),
1327                                   appData.loadPositionFile)) {
1328             DisplayFatalError(_("Bad position file"), 0, 1);
1329             return 0;
1330         }
1331     }
1332     return 1;
1333 }
1334
1335 void
1336 ReserveGame(int gameNr, char resChar)
1337 {
1338     FILE *tf = fopen(appData.tourneyFile, "r+");
1339     char *p, *q, c, buf[MSG_SIZ];
1340     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1341     safeStrCpy(buf, lastMsg, MSG_SIZ);
1342     DisplayMessage(_("Pick new game"), "");
1343     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1344     ParseArgsFromFile(tf);
1345     p = q = appData.results;
1346     if(appData.debugMode) {
1347       char *r = appData.participants;
1348       fprintf(debugFP, "results = '%s'\n", p);
1349       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1350       fprintf(debugFP, "\n");
1351     }
1352     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1353     nextGame = q - p;
1354     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1355     safeStrCpy(q, p, strlen(p) + 2);
1356     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1357     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1358     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1359         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1360         q[nextGame] = '*';
1361     }
1362     fseek(tf, -(strlen(p)+4), SEEK_END);
1363     c = fgetc(tf);
1364     if(c != '"') // depending on DOS or Unix line endings we can be one off
1365          fseek(tf, -(strlen(p)+2), SEEK_END);
1366     else fseek(tf, -(strlen(p)+3), SEEK_END);
1367     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1368     DisplayMessage(buf, "");
1369     free(p); appData.results = q;
1370     if(nextGame <= appData.matchGames && resChar != ' ' &&
1371        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1372         UnloadEngine(&first);  // next game belongs to other pairing;
1373         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1374     }
1375 }
1376
1377 void
1378 MatchEvent(int mode)
1379 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1380         int dummy;
1381         if(matchMode) { // already in match mode: switch it off
1382             abortMatch = TRUE;
1383             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1384             ModeHighlight(); // kludgey way to remove checkmark...
1385             return;
1386         }
1387 //      if(gameMode != BeginningOfGame) {
1388 //          DisplayError(_("You can only start a match from the initial position."), 0);
1389 //          return;
1390 //      }
1391         abortMatch = FALSE;
1392         appData.matchGames = appData.defaultMatchGames;
1393         /* Set up machine vs. machine match */
1394         nextGame = 0;
1395         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1396         if(appData.tourneyFile[0]) {
1397             ReserveGame(-1, 0);
1398             if(nextGame > appData.matchGames) {
1399                 char buf[MSG_SIZ];
1400                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1401                 DisplayError(buf, 0);
1402                 appData.tourneyFile[0] = 0;
1403                 return;
1404             }
1405         } else
1406         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1407             DisplayFatalError(_("Can't have a match with no chess programs"),
1408                               0, 2);
1409             return;
1410         }
1411         matchMode = mode;
1412         matchGame = roundNr = 1;
1413         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1414         NextMatchGame();
1415 }
1416
1417 void
1418 InitBackEnd3 P((void))
1419 {
1420     GameMode initialMode;
1421     char buf[MSG_SIZ];
1422     int err, len;
1423
1424     InitChessProgram(&first, startedFromSetupPosition);
1425
1426     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1427         free(programVersion);
1428         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1429         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1430     }
1431
1432     if (appData.icsActive) {
1433 #ifdef WIN32
1434         /* [DM] Make a console window if needed [HGM] merged ifs */
1435         ConsoleCreate();
1436 #endif
1437         err = establish();
1438         if (err != 0)
1439           {
1440             if (*appData.icsCommPort != NULLCHAR)
1441               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1442                              appData.icsCommPort);
1443             else
1444               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1445                         appData.icsHost, appData.icsPort);
1446
1447             if( (len > MSG_SIZ) && appData.debugMode )
1448               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1449
1450             DisplayFatalError(buf, err, 1);
1451             return;
1452         }
1453         SetICSMode();
1454         telnetISR =
1455           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1456         fromUserISR =
1457           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1458         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1459             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1460     } else if (appData.noChessProgram) {
1461         SetNCPMode();
1462     } else {
1463         SetGNUMode();
1464     }
1465
1466     if (*appData.cmailGameName != NULLCHAR) {
1467         SetCmailMode();
1468         OpenLoopback(&cmailPR);
1469         cmailISR =
1470           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1471     }
1472
1473     ThawUI();
1474     DisplayMessage("", "");
1475     if (StrCaseCmp(appData.initialMode, "") == 0) {
1476       initialMode = BeginningOfGame;
1477       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1478         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1479         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1480         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1481         ModeHighlight();
1482       }
1483     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1484       initialMode = TwoMachinesPlay;
1485     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1486       initialMode = AnalyzeFile;
1487     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1488       initialMode = AnalyzeMode;
1489     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1490       initialMode = MachinePlaysWhite;
1491     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1492       initialMode = MachinePlaysBlack;
1493     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1494       initialMode = EditGame;
1495     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1496       initialMode = EditPosition;
1497     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1498       initialMode = Training;
1499     } else {
1500       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1501       if( (len > MSG_SIZ) && appData.debugMode )
1502         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1503
1504       DisplayFatalError(buf, 0, 2);
1505       return;
1506     }
1507
1508     if (appData.matchMode) {
1509         if(appData.tourneyFile[0]) { // start tourney from command line
1510             FILE *f;
1511             if(f = fopen(appData.tourneyFile, "r")) {
1512                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1513                 fclose(f);
1514             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1515         }
1516         MatchEvent(TRUE);
1517     } else if (*appData.cmailGameName != NULLCHAR) {
1518         /* Set up cmail mode */
1519         ReloadCmailMsgEvent(TRUE);
1520     } else {
1521         /* Set up other modes */
1522         if (initialMode == AnalyzeFile) {
1523           if (*appData.loadGameFile == NULLCHAR) {
1524             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1525             return;
1526           }
1527         }
1528         if (*appData.loadGameFile != NULLCHAR) {
1529             (void) LoadGameFromFile(appData.loadGameFile,
1530                                     appData.loadGameIndex,
1531                                     appData.loadGameFile, TRUE);
1532         } else if (*appData.loadPositionFile != NULLCHAR) {
1533             (void) LoadPositionFromFile(appData.loadPositionFile,
1534                                         appData.loadPositionIndex,
1535                                         appData.loadPositionFile);
1536             /* [HGM] try to make self-starting even after FEN load */
1537             /* to allow automatic setup of fairy variants with wtm */
1538             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1539                 gameMode = BeginningOfGame;
1540                 setboardSpoiledMachineBlack = 1;
1541             }
1542             /* [HGM] loadPos: make that every new game uses the setup */
1543             /* from file as long as we do not switch variant          */
1544             if(!blackPlaysFirst) {
1545                 startedFromPositionFile = TRUE;
1546                 CopyBoard(filePosition, boards[0]);
1547             }
1548         }
1549         if (initialMode == AnalyzeMode) {
1550           if (appData.noChessProgram) {
1551             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1552             return;
1553           }
1554           if (appData.icsActive) {
1555             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1556             return;
1557           }
1558           AnalyzeModeEvent();
1559         } else if (initialMode == AnalyzeFile) {
1560           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1561           ShowThinkingEvent();
1562           AnalyzeFileEvent();
1563           AnalysisPeriodicEvent(1);
1564         } else if (initialMode == MachinePlaysWhite) {
1565           if (appData.noChessProgram) {
1566             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1567                               0, 2);
1568             return;
1569           }
1570           if (appData.icsActive) {
1571             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1572                               0, 2);
1573             return;
1574           }
1575           MachineWhiteEvent();
1576         } else if (initialMode == MachinePlaysBlack) {
1577           if (appData.noChessProgram) {
1578             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1579                               0, 2);
1580             return;
1581           }
1582           if (appData.icsActive) {
1583             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1584                               0, 2);
1585             return;
1586           }
1587           MachineBlackEvent();
1588         } else if (initialMode == TwoMachinesPlay) {
1589           if (appData.noChessProgram) {
1590             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1591                               0, 2);
1592             return;
1593           }
1594           if (appData.icsActive) {
1595             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1596                               0, 2);
1597             return;
1598           }
1599           TwoMachinesEvent();
1600         } else if (initialMode == EditGame) {
1601           EditGameEvent();
1602         } else if (initialMode == EditPosition) {
1603           EditPositionEvent();
1604         } else if (initialMode == Training) {
1605           if (*appData.loadGameFile == NULLCHAR) {
1606             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1607             return;
1608           }
1609           TrainingEvent();
1610         }
1611     }
1612 }
1613
1614 /*
1615  * Establish will establish a contact to a remote host.port.
1616  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1617  *  used to talk to the host.
1618  * Returns 0 if okay, error code if not.
1619  */
1620 int
1621 establish()
1622 {
1623     char buf[MSG_SIZ];
1624
1625     if (*appData.icsCommPort != NULLCHAR) {
1626         /* Talk to the host through a serial comm port */
1627         return OpenCommPort(appData.icsCommPort, &icsPR);
1628
1629     } else if (*appData.gateway != NULLCHAR) {
1630         if (*appData.remoteShell == NULLCHAR) {
1631             /* Use the rcmd protocol to run telnet program on a gateway host */
1632             snprintf(buf, sizeof(buf), "%s %s %s",
1633                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1634             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1635
1636         } else {
1637             /* Use the rsh program to run telnet program on a gateway host */
1638             if (*appData.remoteUser == NULLCHAR) {
1639                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1640                         appData.gateway, appData.telnetProgram,
1641                         appData.icsHost, appData.icsPort);
1642             } else {
1643                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1644                         appData.remoteShell, appData.gateway,
1645                         appData.remoteUser, appData.telnetProgram,
1646                         appData.icsHost, appData.icsPort);
1647             }
1648             return StartChildProcess(buf, "", &icsPR);
1649
1650         }
1651     } else if (appData.useTelnet) {
1652         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1653
1654     } else {
1655         /* TCP socket interface differs somewhat between
1656            Unix and NT; handle details in the front end.
1657            */
1658         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1659     }
1660 }
1661
1662 void EscapeExpand(char *p, char *q)
1663 {       // [HGM] initstring: routine to shape up string arguments
1664         while(*p++ = *q++) if(p[-1] == '\\')
1665             switch(*q++) {
1666                 case 'n': p[-1] = '\n'; break;
1667                 case 'r': p[-1] = '\r'; break;
1668                 case 't': p[-1] = '\t'; break;
1669                 case '\\': p[-1] = '\\'; break;
1670                 case 0: *p = 0; return;
1671                 default: p[-1] = q[-1]; break;
1672             }
1673 }
1674
1675 void
1676 show_bytes(fp, buf, count)
1677      FILE *fp;
1678      char *buf;
1679      int count;
1680 {
1681     while (count--) {
1682         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1683             fprintf(fp, "\\%03o", *buf & 0xff);
1684         } else {
1685             putc(*buf, fp);
1686         }
1687         buf++;
1688     }
1689     fflush(fp);
1690 }
1691
1692 /* Returns an errno value */
1693 int
1694 OutputMaybeTelnet(pr, message, count, outError)
1695      ProcRef pr;
1696      char *message;
1697      int count;
1698      int *outError;
1699 {
1700     char buf[8192], *p, *q, *buflim;
1701     int left, newcount, outcount;
1702
1703     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1704         *appData.gateway != NULLCHAR) {
1705         if (appData.debugMode) {
1706             fprintf(debugFP, ">ICS: ");
1707             show_bytes(debugFP, message, count);
1708             fprintf(debugFP, "\n");
1709         }
1710         return OutputToProcess(pr, message, count, outError);
1711     }
1712
1713     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1714     p = message;
1715     q = buf;
1716     left = count;
1717     newcount = 0;
1718     while (left) {
1719         if (q >= buflim) {
1720             if (appData.debugMode) {
1721                 fprintf(debugFP, ">ICS: ");
1722                 show_bytes(debugFP, buf, newcount);
1723                 fprintf(debugFP, "\n");
1724             }
1725             outcount = OutputToProcess(pr, buf, newcount, outError);
1726             if (outcount < newcount) return -1; /* to be sure */
1727             q = buf;
1728             newcount = 0;
1729         }
1730         if (*p == '\n') {
1731             *q++ = '\r';
1732             newcount++;
1733         } else if (((unsigned char) *p) == TN_IAC) {
1734             *q++ = (char) TN_IAC;
1735             newcount ++;
1736         }
1737         *q++ = *p++;
1738         newcount++;
1739         left--;
1740     }
1741     if (appData.debugMode) {
1742         fprintf(debugFP, ">ICS: ");
1743         show_bytes(debugFP, buf, newcount);
1744         fprintf(debugFP, "\n");
1745     }
1746     outcount = OutputToProcess(pr, buf, newcount, outError);
1747     if (outcount < newcount) return -1; /* to be sure */
1748     return count;
1749 }
1750
1751 void
1752 read_from_player(isr, closure, message, count, error)
1753      InputSourceRef isr;
1754      VOIDSTAR closure;
1755      char *message;
1756      int count;
1757      int error;
1758 {
1759     int outError, outCount;
1760     static int gotEof = 0;
1761
1762     /* Pass data read from player on to ICS */
1763     if (count > 0) {
1764         gotEof = 0;
1765         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1766         if (outCount < count) {
1767             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1768         }
1769     } else if (count < 0) {
1770         RemoveInputSource(isr);
1771         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1772     } else if (gotEof++ > 0) {
1773         RemoveInputSource(isr);
1774         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1775     }
1776 }
1777
1778 void
1779 KeepAlive()
1780 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1781     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1782     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1783     SendToICS("date\n");
1784     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1785 }
1786
1787 /* added routine for printf style output to ics */
1788 void ics_printf(char *format, ...)
1789 {
1790     char buffer[MSG_SIZ];
1791     va_list args;
1792
1793     va_start(args, format);
1794     vsnprintf(buffer, sizeof(buffer), format, args);
1795     buffer[sizeof(buffer)-1] = '\0';
1796     SendToICS(buffer);
1797     va_end(args);
1798 }
1799
1800 void
1801 SendToICS(s)
1802      char *s;
1803 {
1804     int count, outCount, outError;
1805
1806     if (icsPR == NULL) return;
1807
1808     count = strlen(s);
1809     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1810     if (outCount < count) {
1811         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812     }
1813 }
1814
1815 /* This is used for sending logon scripts to the ICS. Sending
1816    without a delay causes problems when using timestamp on ICC
1817    (at least on my machine). */
1818 void
1819 SendToICSDelayed(s,msdelay)
1820      char *s;
1821      long msdelay;
1822 {
1823     int count, outCount, outError;
1824
1825     if (icsPR == NULL) return;
1826
1827     count = strlen(s);
1828     if (appData.debugMode) {
1829         fprintf(debugFP, ">ICS: ");
1830         show_bytes(debugFP, s, count);
1831         fprintf(debugFP, "\n");
1832     }
1833     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1834                                       msdelay);
1835     if (outCount < count) {
1836         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1837     }
1838 }
1839
1840
1841 /* Remove all highlighting escape sequences in s
1842    Also deletes any suffix starting with '('
1843    */
1844 char *
1845 StripHighlightAndTitle(s)
1846      char *s;
1847 {
1848     static char retbuf[MSG_SIZ];
1849     char *p = retbuf;
1850
1851     while (*s != NULLCHAR) {
1852         while (*s == '\033') {
1853             while (*s != NULLCHAR && !isalpha(*s)) s++;
1854             if (*s != NULLCHAR) s++;
1855         }
1856         while (*s != NULLCHAR && *s != '\033') {
1857             if (*s == '(' || *s == '[') {
1858                 *p = NULLCHAR;
1859                 return retbuf;
1860             }
1861             *p++ = *s++;
1862         }
1863     }
1864     *p = NULLCHAR;
1865     return retbuf;
1866 }
1867
1868 /* Remove all highlighting escape sequences in s */
1869 char *
1870 StripHighlight(s)
1871      char *s;
1872 {
1873     static char retbuf[MSG_SIZ];
1874     char *p = retbuf;
1875
1876     while (*s != NULLCHAR) {
1877         while (*s == '\033') {
1878             while (*s != NULLCHAR && !isalpha(*s)) s++;
1879             if (*s != NULLCHAR) s++;
1880         }
1881         while (*s != NULLCHAR && *s != '\033') {
1882             *p++ = *s++;
1883         }
1884     }
1885     *p = NULLCHAR;
1886     return retbuf;
1887 }
1888
1889 char *variantNames[] = VARIANT_NAMES;
1890 char *
1891 VariantName(v)
1892      VariantClass v;
1893 {
1894     return variantNames[v];
1895 }
1896
1897
1898 /* Identify a variant from the strings the chess servers use or the
1899    PGN Variant tag names we use. */
1900 VariantClass
1901 StringToVariant(e)
1902      char *e;
1903 {
1904     char *p;
1905     int wnum = -1;
1906     VariantClass v = VariantNormal;
1907     int i, found = FALSE;
1908     char buf[MSG_SIZ];
1909     int len;
1910
1911     if (!e) return v;
1912
1913     /* [HGM] skip over optional board-size prefixes */
1914     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1915         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1916         while( *e++ != '_');
1917     }
1918
1919     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1920         v = VariantNormal;
1921         found = TRUE;
1922     } else
1923     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1924       if (StrCaseStr(e, variantNames[i])) {
1925         v = (VariantClass) i;
1926         found = TRUE;
1927         break;
1928       }
1929     }
1930
1931     if (!found) {
1932       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1933           || StrCaseStr(e, "wild/fr")
1934           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1935         v = VariantFischeRandom;
1936       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1937                  (i = 1, p = StrCaseStr(e, "w"))) {
1938         p += i;
1939         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1940         if (isdigit(*p)) {
1941           wnum = atoi(p);
1942         } else {
1943           wnum = -1;
1944         }
1945         switch (wnum) {
1946         case 0: /* FICS only, actually */
1947         case 1:
1948           /* Castling legal even if K starts on d-file */
1949           v = VariantWildCastle;
1950           break;
1951         case 2:
1952         case 3:
1953         case 4:
1954           /* Castling illegal even if K & R happen to start in
1955              normal positions. */
1956           v = VariantNoCastle;
1957           break;
1958         case 5:
1959         case 7:
1960         case 8:
1961         case 10:
1962         case 11:
1963         case 12:
1964         case 13:
1965         case 14:
1966         case 15:
1967         case 18:
1968         case 19:
1969           /* Castling legal iff K & R start in normal positions */
1970           v = VariantNormal;
1971           break;
1972         case 6:
1973         case 20:
1974         case 21:
1975           /* Special wilds for position setup; unclear what to do here */
1976           v = VariantLoadable;
1977           break;
1978         case 9:
1979           /* Bizarre ICC game */
1980           v = VariantTwoKings;
1981           break;
1982         case 16:
1983           v = VariantKriegspiel;
1984           break;
1985         case 17:
1986           v = VariantLosers;
1987           break;
1988         case 22:
1989           v = VariantFischeRandom;
1990           break;
1991         case 23:
1992           v = VariantCrazyhouse;
1993           break;
1994         case 24:
1995           v = VariantBughouse;
1996           break;
1997         case 25:
1998           v = Variant3Check;
1999           break;
2000         case 26:
2001           /* Not quite the same as FICS suicide! */
2002           v = VariantGiveaway;
2003           break;
2004         case 27:
2005           v = VariantAtomic;
2006           break;
2007         case 28:
2008           v = VariantShatranj;
2009           break;
2010
2011         /* Temporary names for future ICC types.  The name *will* change in
2012            the next xboard/WinBoard release after ICC defines it. */
2013         case 29:
2014           v = Variant29;
2015           break;
2016         case 30:
2017           v = Variant30;
2018           break;
2019         case 31:
2020           v = Variant31;
2021           break;
2022         case 32:
2023           v = Variant32;
2024           break;
2025         case 33:
2026           v = Variant33;
2027           break;
2028         case 34:
2029           v = Variant34;
2030           break;
2031         case 35:
2032           v = Variant35;
2033           break;
2034         case 36:
2035           v = Variant36;
2036           break;
2037         case 37:
2038           v = VariantShogi;
2039           break;
2040         case 38:
2041           v = VariantXiangqi;
2042           break;
2043         case 39:
2044           v = VariantCourier;
2045           break;
2046         case 40:
2047           v = VariantGothic;
2048           break;
2049         case 41:
2050           v = VariantCapablanca;
2051           break;
2052         case 42:
2053           v = VariantKnightmate;
2054           break;
2055         case 43:
2056           v = VariantFairy;
2057           break;
2058         case 44:
2059           v = VariantCylinder;
2060           break;
2061         case 45:
2062           v = VariantFalcon;
2063           break;
2064         case 46:
2065           v = VariantCapaRandom;
2066           break;
2067         case 47:
2068           v = VariantBerolina;
2069           break;
2070         case 48:
2071           v = VariantJanus;
2072           break;
2073         case 49:
2074           v = VariantSuper;
2075           break;
2076         case 50:
2077           v = VariantGreat;
2078           break;
2079         case -1:
2080           /* Found "wild" or "w" in the string but no number;
2081              must assume it's normal chess. */
2082           v = VariantNormal;
2083           break;
2084         default:
2085           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2086           if( (len > MSG_SIZ) && appData.debugMode )
2087             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2088
2089           DisplayError(buf, 0);
2090           v = VariantUnknown;
2091           break;
2092         }
2093       }
2094     }
2095     if (appData.debugMode) {
2096       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2097               e, wnum, VariantName(v));
2098     }
2099     return v;
2100 }
2101
2102 static int leftover_start = 0, leftover_len = 0;
2103 char star_match[STAR_MATCH_N][MSG_SIZ];
2104
2105 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2106    advance *index beyond it, and set leftover_start to the new value of
2107    *index; else return FALSE.  If pattern contains the character '*', it
2108    matches any sequence of characters not containing '\r', '\n', or the
2109    character following the '*' (if any), and the matched sequence(s) are
2110    copied into star_match.
2111    */
2112 int
2113 looking_at(buf, index, pattern)
2114      char *buf;
2115      int *index;
2116      char *pattern;
2117 {
2118     char *bufp = &buf[*index], *patternp = pattern;
2119     int star_count = 0;
2120     char *matchp = star_match[0];
2121
2122     for (;;) {
2123         if (*patternp == NULLCHAR) {
2124             *index = leftover_start = bufp - buf;
2125             *matchp = NULLCHAR;
2126             return TRUE;
2127         }
2128         if (*bufp == NULLCHAR) return FALSE;
2129         if (*patternp == '*') {
2130             if (*bufp == *(patternp + 1)) {
2131                 *matchp = NULLCHAR;
2132                 matchp = star_match[++star_count];
2133                 patternp += 2;
2134                 bufp++;
2135                 continue;
2136             } else if (*bufp == '\n' || *bufp == '\r') {
2137                 patternp++;
2138                 if (*patternp == NULLCHAR)
2139                   continue;
2140                 else
2141                   return FALSE;
2142             } else {
2143                 *matchp++ = *bufp++;
2144                 continue;
2145             }
2146         }
2147         if (*patternp != *bufp) return FALSE;
2148         patternp++;
2149         bufp++;
2150     }
2151 }
2152
2153 void
2154 SendToPlayer(data, length)
2155      char *data;
2156      int length;
2157 {
2158     int error, outCount;
2159     outCount = OutputToProcess(NoProc, data, length, &error);
2160     if (outCount < length) {
2161         DisplayFatalError(_("Error writing to display"), error, 1);
2162     }
2163 }
2164
2165 void
2166 PackHolding(packed, holding)
2167      char packed[];
2168      char *holding;
2169 {
2170     char *p = holding;
2171     char *q = packed;
2172     int runlength = 0;
2173     int curr = 9999;
2174     do {
2175         if (*p == curr) {
2176             runlength++;
2177         } else {
2178             switch (runlength) {
2179               case 0:
2180                 break;
2181               case 1:
2182                 *q++ = curr;
2183                 break;
2184               case 2:
2185                 *q++ = curr;
2186                 *q++ = curr;
2187                 break;
2188               default:
2189                 sprintf(q, "%d", runlength);
2190                 while (*q) q++;
2191                 *q++ = curr;
2192                 break;
2193             }
2194             runlength = 1;
2195             curr = *p;
2196         }
2197     } while (*p++);
2198     *q = NULLCHAR;
2199 }
2200
2201 /* Telnet protocol requests from the front end */
2202 void
2203 TelnetRequest(ddww, option)
2204      unsigned char ddww, option;
2205 {
2206     unsigned char msg[3];
2207     int outCount, outError;
2208
2209     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2210
2211     if (appData.debugMode) {
2212         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2213         switch (ddww) {
2214           case TN_DO:
2215             ddwwStr = "DO";
2216             break;
2217           case TN_DONT:
2218             ddwwStr = "DONT";
2219             break;
2220           case TN_WILL:
2221             ddwwStr = "WILL";
2222             break;
2223           case TN_WONT:
2224             ddwwStr = "WONT";
2225             break;
2226           default:
2227             ddwwStr = buf1;
2228             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2229             break;
2230         }
2231         switch (option) {
2232           case TN_ECHO:
2233             optionStr = "ECHO";
2234             break;
2235           default:
2236             optionStr = buf2;
2237             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2238             break;
2239         }
2240         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2241     }
2242     msg[0] = TN_IAC;
2243     msg[1] = ddww;
2244     msg[2] = option;
2245     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2246     if (outCount < 3) {
2247         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2248     }
2249 }
2250
2251 void
2252 DoEcho()
2253 {
2254     if (!appData.icsActive) return;
2255     TelnetRequest(TN_DO, TN_ECHO);
2256 }
2257
2258 void
2259 DontEcho()
2260 {
2261     if (!appData.icsActive) return;
2262     TelnetRequest(TN_DONT, TN_ECHO);
2263 }
2264
2265 void
2266 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2267 {
2268     /* put the holdings sent to us by the server on the board holdings area */
2269     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2270     char p;
2271     ChessSquare piece;
2272
2273     if(gameInfo.holdingsWidth < 2)  return;
2274     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2275         return; // prevent overwriting by pre-board holdings
2276
2277     if( (int)lowestPiece >= BlackPawn ) {
2278         holdingsColumn = 0;
2279         countsColumn = 1;
2280         holdingsStartRow = BOARD_HEIGHT-1;
2281         direction = -1;
2282     } else {
2283         holdingsColumn = BOARD_WIDTH-1;
2284         countsColumn = BOARD_WIDTH-2;
2285         holdingsStartRow = 0;
2286         direction = 1;
2287     }
2288
2289     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2290         board[i][holdingsColumn] = EmptySquare;
2291         board[i][countsColumn]   = (ChessSquare) 0;
2292     }
2293     while( (p=*holdings++) != NULLCHAR ) {
2294         piece = CharToPiece( ToUpper(p) );
2295         if(piece == EmptySquare) continue;
2296         /*j = (int) piece - (int) WhitePawn;*/
2297         j = PieceToNumber(piece);
2298         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2299         if(j < 0) continue;               /* should not happen */
2300         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2301         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2302         board[holdingsStartRow+j*direction][countsColumn]++;
2303     }
2304 }
2305
2306
2307 void
2308 VariantSwitch(Board board, VariantClass newVariant)
2309 {
2310    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2311    static Board oldBoard;
2312
2313    startedFromPositionFile = FALSE;
2314    if(gameInfo.variant == newVariant) return;
2315
2316    /* [HGM] This routine is called each time an assignment is made to
2317     * gameInfo.variant during a game, to make sure the board sizes
2318     * are set to match the new variant. If that means adding or deleting
2319     * holdings, we shift the playing board accordingly
2320     * This kludge is needed because in ICS observe mode, we get boards
2321     * of an ongoing game without knowing the variant, and learn about the
2322     * latter only later. This can be because of the move list we requested,
2323     * in which case the game history is refilled from the beginning anyway,
2324     * but also when receiving holdings of a crazyhouse game. In the latter
2325     * case we want to add those holdings to the already received position.
2326     */
2327
2328
2329    if (appData.debugMode) {
2330      fprintf(debugFP, "Switch board from %s to %s\n",
2331              VariantName(gameInfo.variant), VariantName(newVariant));
2332      setbuf(debugFP, NULL);
2333    }
2334    shuffleOpenings = 0;       /* [HGM] shuffle */
2335    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2336    switch(newVariant)
2337      {
2338      case VariantShogi:
2339        newWidth = 9;  newHeight = 9;
2340        gameInfo.holdingsSize = 7;
2341      case VariantBughouse:
2342      case VariantCrazyhouse:
2343        newHoldingsWidth = 2; break;
2344      case VariantGreat:
2345        newWidth = 10;
2346      case VariantSuper:
2347        newHoldingsWidth = 2;
2348        gameInfo.holdingsSize = 8;
2349        break;
2350      case VariantGothic:
2351      case VariantCapablanca:
2352      case VariantCapaRandom:
2353        newWidth = 10;
2354      default:
2355        newHoldingsWidth = gameInfo.holdingsSize = 0;
2356      };
2357
2358    if(newWidth  != gameInfo.boardWidth  ||
2359       newHeight != gameInfo.boardHeight ||
2360       newHoldingsWidth != gameInfo.holdingsWidth ) {
2361
2362      /* shift position to new playing area, if needed */
2363      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2364        for(i=0; i<BOARD_HEIGHT; i++)
2365          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2366            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2367              board[i][j];
2368        for(i=0; i<newHeight; i++) {
2369          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2370          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2371        }
2372      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2373        for(i=0; i<BOARD_HEIGHT; i++)
2374          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2375            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2376              board[i][j];
2377      }
2378      gameInfo.boardWidth  = newWidth;
2379      gameInfo.boardHeight = newHeight;
2380      gameInfo.holdingsWidth = newHoldingsWidth;
2381      gameInfo.variant = newVariant;
2382      InitDrawingSizes(-2, 0);
2383    } else gameInfo.variant = newVariant;
2384    CopyBoard(oldBoard, board);   // remember correctly formatted board
2385      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2386    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2387 }
2388
2389 static int loggedOn = FALSE;
2390
2391 /*-- Game start info cache: --*/
2392 int gs_gamenum;
2393 char gs_kind[MSG_SIZ];
2394 static char player1Name[128] = "";
2395 static char player2Name[128] = "";
2396 static char cont_seq[] = "\n\\   ";
2397 static int player1Rating = -1;
2398 static int player2Rating = -1;
2399 /*----------------------------*/
2400
2401 ColorClass curColor = ColorNormal;
2402 int suppressKibitz = 0;
2403
2404 // [HGM] seekgraph
2405 Boolean soughtPending = FALSE;
2406 Boolean seekGraphUp;
2407 #define MAX_SEEK_ADS 200
2408 #define SQUARE 0x80
2409 char *seekAdList[MAX_SEEK_ADS];
2410 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2411 float tcList[MAX_SEEK_ADS];
2412 char colorList[MAX_SEEK_ADS];
2413 int nrOfSeekAds = 0;
2414 int minRating = 1010, maxRating = 2800;
2415 int hMargin = 10, vMargin = 20, h, w;
2416 extern int squareSize, lineGap;
2417
2418 void
2419 PlotSeekAd(int i)
2420 {
2421         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2422         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2423         if(r < minRating+100 && r >=0 ) r = minRating+100;
2424         if(r > maxRating) r = maxRating;
2425         if(tc < 1.) tc = 1.;
2426         if(tc > 95.) tc = 95.;
2427         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2428         y = ((double)r - minRating)/(maxRating - minRating)
2429             * (h-vMargin-squareSize/8-1) + vMargin;
2430         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2431         if(strstr(seekAdList[i], " u ")) color = 1;
2432         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2433            !strstr(seekAdList[i], "bullet") &&
2434            !strstr(seekAdList[i], "blitz") &&
2435            !strstr(seekAdList[i], "standard") ) color = 2;
2436         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2437         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2438 }
2439
2440 void
2441 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2442 {
2443         char buf[MSG_SIZ], *ext = "";
2444         VariantClass v = StringToVariant(type);
2445         if(strstr(type, "wild")) {
2446             ext = type + 4; // append wild number
2447             if(v == VariantFischeRandom) type = "chess960"; else
2448             if(v == VariantLoadable) type = "setup"; else
2449             type = VariantName(v);
2450         }
2451         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2452         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2453             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2454             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2455             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2456             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2457             seekNrList[nrOfSeekAds] = nr;
2458             zList[nrOfSeekAds] = 0;
2459             seekAdList[nrOfSeekAds++] = StrSave(buf);
2460             if(plot) PlotSeekAd(nrOfSeekAds-1);
2461         }
2462 }
2463
2464 void
2465 EraseSeekDot(int i)
2466 {
2467     int x = xList[i], y = yList[i], d=squareSize/4, k;
2468     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2469     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2470     // now replot every dot that overlapped
2471     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2472         int xx = xList[k], yy = yList[k];
2473         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2474             DrawSeekDot(xx, yy, colorList[k]);
2475     }
2476 }
2477
2478 void
2479 RemoveSeekAd(int nr)
2480 {
2481         int i;
2482         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2483             EraseSeekDot(i);
2484             if(seekAdList[i]) free(seekAdList[i]);
2485             seekAdList[i] = seekAdList[--nrOfSeekAds];
2486             seekNrList[i] = seekNrList[nrOfSeekAds];
2487             ratingList[i] = ratingList[nrOfSeekAds];
2488             colorList[i]  = colorList[nrOfSeekAds];
2489             tcList[i] = tcList[nrOfSeekAds];
2490             xList[i]  = xList[nrOfSeekAds];
2491             yList[i]  = yList[nrOfSeekAds];
2492             zList[i]  = zList[nrOfSeekAds];
2493             seekAdList[nrOfSeekAds] = NULL;
2494             break;
2495         }
2496 }
2497
2498 Boolean
2499 MatchSoughtLine(char *line)
2500 {
2501     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2502     int nr, base, inc, u=0; char dummy;
2503
2504     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2505        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2506        (u=1) &&
2507        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2508         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2509         // match: compact and save the line
2510         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2511         return TRUE;
2512     }
2513     return FALSE;
2514 }
2515
2516 int
2517 DrawSeekGraph()
2518 {
2519     int i;
2520     if(!seekGraphUp) return FALSE;
2521     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2522     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2523
2524     DrawSeekBackground(0, 0, w, h);
2525     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2526     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2527     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2528         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2529         yy = h-1-yy;
2530         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2531         if(i%500 == 0) {
2532             char buf[MSG_SIZ];
2533             snprintf(buf, MSG_SIZ, "%d", i);
2534             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2535         }
2536     }
2537     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2538     for(i=1; i<100; i+=(i<10?1:5)) {
2539         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2540         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2541         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2542             char buf[MSG_SIZ];
2543             snprintf(buf, MSG_SIZ, "%d", i);
2544             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2545         }
2546     }
2547     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2548     return TRUE;
2549 }
2550
2551 int SeekGraphClick(ClickType click, int x, int y, int moving)
2552 {
2553     static int lastDown = 0, displayed = 0, lastSecond;
2554     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2555         if(click == Release || moving) return FALSE;
2556         nrOfSeekAds = 0;
2557         soughtPending = TRUE;
2558         SendToICS(ics_prefix);
2559         SendToICS("sought\n"); // should this be "sought all"?
2560     } else { // issue challenge based on clicked ad
2561         int dist = 10000; int i, closest = 0, second = 0;
2562         for(i=0; i<nrOfSeekAds; i++) {
2563             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2564             if(d < dist) { dist = d; closest = i; }
2565             second += (d - zList[i] < 120); // count in-range ads
2566             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2567         }
2568         if(dist < 120) {
2569             char buf[MSG_SIZ];
2570             second = (second > 1);
2571             if(displayed != closest || second != lastSecond) {
2572                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2573                 lastSecond = second; displayed = closest;
2574             }
2575             if(click == Press) {
2576                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2577                 lastDown = closest;
2578                 return TRUE;
2579             } // on press 'hit', only show info
2580             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2581             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2582             SendToICS(ics_prefix);
2583             SendToICS(buf);
2584             return TRUE; // let incoming board of started game pop down the graph
2585         } else if(click == Release) { // release 'miss' is ignored
2586             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2587             if(moving == 2) { // right up-click
2588                 nrOfSeekAds = 0; // refresh graph
2589                 soughtPending = TRUE;
2590                 SendToICS(ics_prefix);
2591                 SendToICS("sought\n"); // should this be "sought all"?
2592             }
2593             return TRUE;
2594         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2595         // press miss or release hit 'pop down' seek graph
2596         seekGraphUp = FALSE;
2597         DrawPosition(TRUE, NULL);
2598     }
2599     return TRUE;
2600 }
2601
2602 void
2603 read_from_ics(isr, closure, data, count, error)
2604      InputSourceRef isr;
2605      VOIDSTAR closure;
2606      char *data;
2607      int count;
2608      int error;
2609 {
2610 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2611 #define STARTED_NONE 0
2612 #define STARTED_MOVES 1
2613 #define STARTED_BOARD 2
2614 #define STARTED_OBSERVE 3
2615 #define STARTED_HOLDINGS 4
2616 #define STARTED_CHATTER 5
2617 #define STARTED_COMMENT 6
2618 #define STARTED_MOVES_NOHIDE 7
2619
2620     static int started = STARTED_NONE;
2621     static char parse[20000];
2622     static int parse_pos = 0;
2623     static char buf[BUF_SIZE + 1];
2624     static int firstTime = TRUE, intfSet = FALSE;
2625     static ColorClass prevColor = ColorNormal;
2626     static int savingComment = FALSE;
2627     static int cmatch = 0; // continuation sequence match
2628     char *bp;
2629     char str[MSG_SIZ];
2630     int i, oldi;
2631     int buf_len;
2632     int next_out;
2633     int tkind;
2634     int backup;    /* [DM] For zippy color lines */
2635     char *p;
2636     char talker[MSG_SIZ]; // [HGM] chat
2637     int channel;
2638
2639     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2640
2641     if (appData.debugMode) {
2642       if (!error) {
2643         fprintf(debugFP, "<ICS: ");
2644         show_bytes(debugFP, data, count);
2645         fprintf(debugFP, "\n");
2646       }
2647     }
2648
2649     if (appData.debugMode) { int f = forwardMostMove;
2650         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2651                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2652                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2653     }
2654     if (count > 0) {
2655         /* If last read ended with a partial line that we couldn't parse,
2656            prepend it to the new read and try again. */
2657         if (leftover_len > 0) {
2658             for (i=0; i<leftover_len; i++)
2659               buf[i] = buf[leftover_start + i];
2660         }
2661
2662     /* copy new characters into the buffer */
2663     bp = buf + leftover_len;
2664     buf_len=leftover_len;
2665     for (i=0; i<count; i++)
2666     {
2667         // ignore these
2668         if (data[i] == '\r')
2669             continue;
2670
2671         // join lines split by ICS?
2672         if (!appData.noJoin)
2673         {
2674             /*
2675                 Joining just consists of finding matches against the
2676                 continuation sequence, and discarding that sequence
2677                 if found instead of copying it.  So, until a match
2678                 fails, there's nothing to do since it might be the
2679                 complete sequence, and thus, something we don't want
2680                 copied.
2681             */
2682             if (data[i] == cont_seq[cmatch])
2683             {
2684                 cmatch++;
2685                 if (cmatch == strlen(cont_seq))
2686                 {
2687                     cmatch = 0; // complete match.  just reset the counter
2688
2689                     /*
2690                         it's possible for the ICS to not include the space
2691                         at the end of the last word, making our [correct]
2692                         join operation fuse two separate words.  the server
2693                         does this when the space occurs at the width setting.
2694                     */
2695                     if (!buf_len || buf[buf_len-1] != ' ')
2696                     {
2697                         *bp++ = ' ';
2698                         buf_len++;
2699                     }
2700                 }
2701                 continue;
2702             }
2703             else if (cmatch)
2704             {
2705                 /*
2706                     match failed, so we have to copy what matched before
2707                     falling through and copying this character.  In reality,
2708                     this will only ever be just the newline character, but
2709                     it doesn't hurt to be precise.
2710                 */
2711                 strncpy(bp, cont_seq, cmatch);
2712                 bp += cmatch;
2713                 buf_len += cmatch;
2714                 cmatch = 0;
2715             }
2716         }
2717
2718         // copy this char
2719         *bp++ = data[i];
2720         buf_len++;
2721     }
2722
2723         buf[buf_len] = NULLCHAR;
2724 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2725         next_out = 0;
2726         leftover_start = 0;
2727
2728         i = 0;
2729         while (i < buf_len) {
2730             /* Deal with part of the TELNET option negotiation
2731                protocol.  We refuse to do anything beyond the
2732                defaults, except that we allow the WILL ECHO option,
2733                which ICS uses to turn off password echoing when we are
2734                directly connected to it.  We reject this option
2735                if localLineEditing mode is on (always on in xboard)
2736                and we are talking to port 23, which might be a real
2737                telnet server that will try to keep WILL ECHO on permanently.
2738              */
2739             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2740                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2741                 unsigned char option;
2742                 oldi = i;
2743                 switch ((unsigned char) buf[++i]) {
2744                   case TN_WILL:
2745                     if (appData.debugMode)
2746                       fprintf(debugFP, "\n<WILL ");
2747                     switch (option = (unsigned char) buf[++i]) {
2748                       case TN_ECHO:
2749                         if (appData.debugMode)
2750                           fprintf(debugFP, "ECHO ");
2751                         /* Reply only if this is a change, according
2752                            to the protocol rules. */
2753                         if (remoteEchoOption) break;
2754                         if (appData.localLineEditing &&
2755                             atoi(appData.icsPort) == TN_PORT) {
2756                             TelnetRequest(TN_DONT, TN_ECHO);
2757                         } else {
2758                             EchoOff();
2759                             TelnetRequest(TN_DO, TN_ECHO);
2760                             remoteEchoOption = TRUE;
2761                         }
2762                         break;
2763                       default:
2764                         if (appData.debugMode)
2765                           fprintf(debugFP, "%d ", option);
2766                         /* Whatever this is, we don't want it. */
2767                         TelnetRequest(TN_DONT, option);
2768                         break;
2769                     }
2770                     break;
2771                   case TN_WONT:
2772                     if (appData.debugMode)
2773                       fprintf(debugFP, "\n<WONT ");
2774                     switch (option = (unsigned char) buf[++i]) {
2775                       case TN_ECHO:
2776                         if (appData.debugMode)
2777                           fprintf(debugFP, "ECHO ");
2778                         /* Reply only if this is a change, according
2779                            to the protocol rules. */
2780                         if (!remoteEchoOption) break;
2781                         EchoOn();
2782                         TelnetRequest(TN_DONT, TN_ECHO);
2783                         remoteEchoOption = FALSE;
2784                         break;
2785                       default:
2786                         if (appData.debugMode)
2787                           fprintf(debugFP, "%d ", (unsigned char) option);
2788                         /* Whatever this is, it must already be turned
2789                            off, because we never agree to turn on
2790                            anything non-default, so according to the
2791                            protocol rules, we don't reply. */
2792                         break;
2793                     }
2794                     break;
2795                   case TN_DO:
2796                     if (appData.debugMode)
2797                       fprintf(debugFP, "\n<DO ");
2798                     switch (option = (unsigned char) buf[++i]) {
2799                       default:
2800                         /* Whatever this is, we refuse to do it. */
2801                         if (appData.debugMode)
2802                           fprintf(debugFP, "%d ", option);
2803                         TelnetRequest(TN_WONT, option);
2804                         break;
2805                     }
2806                     break;
2807                   case TN_DONT:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<DONT ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       default:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "%d ", option);
2814                         /* Whatever this is, we are already not doing
2815                            it, because we never agree to do anything
2816                            non-default, so according to the protocol
2817                            rules, we don't reply. */
2818                         break;
2819                     }
2820                     break;
2821                   case TN_IAC:
2822                     if (appData.debugMode)
2823                       fprintf(debugFP, "\n<IAC ");
2824                     /* Doubled IAC; pass it through */
2825                     i--;
2826                     break;
2827                   default:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2830                     /* Drop all other telnet commands on the floor */
2831                     break;
2832                 }
2833                 if (oldi > next_out)
2834                   SendToPlayer(&buf[next_out], oldi - next_out);
2835                 if (++i > next_out)
2836                   next_out = i;
2837                 continue;
2838             }
2839
2840             /* OK, this at least will *usually* work */
2841             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2842                 loggedOn = TRUE;
2843             }
2844
2845             if (loggedOn && !intfSet) {
2846                 if (ics_type == ICS_ICC) {
2847                   snprintf(str, MSG_SIZ,
2848                           "/set-quietly interface %s\n/set-quietly style 12\n",
2849                           programVersion);
2850                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2851                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2852                 } else if (ics_type == ICS_CHESSNET) {
2853                   snprintf(str, MSG_SIZ, "/style 12\n");
2854                 } else {
2855                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2856                   strcat(str, programVersion);
2857                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2858                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2859                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2860 #ifdef WIN32
2861                   strcat(str, "$iset nohighlight 1\n");
2862 #endif
2863                   strcat(str, "$iset lock 1\n$style 12\n");
2864                 }
2865                 SendToICS(str);
2866                 NotifyFrontendLogin();
2867                 intfSet = TRUE;
2868             }
2869
2870             if (started == STARTED_COMMENT) {
2871                 /* Accumulate characters in comment */
2872                 parse[parse_pos++] = buf[i];
2873                 if (buf[i] == '\n') {
2874                     parse[parse_pos] = NULLCHAR;
2875                     if(chattingPartner>=0) {
2876                         char mess[MSG_SIZ];
2877                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2878                         OutputChatMessage(chattingPartner, mess);
2879                         chattingPartner = -1;
2880                         next_out = i+1; // [HGM] suppress printing in ICS window
2881                     } else
2882                     if(!suppressKibitz) // [HGM] kibitz
2883                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2884                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2885                         int nrDigit = 0, nrAlph = 0, j;
2886                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2887                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2888                         parse[parse_pos] = NULLCHAR;
2889                         // try to be smart: if it does not look like search info, it should go to
2890                         // ICS interaction window after all, not to engine-output window.
2891                         for(j=0; j<parse_pos; j++) { // count letters and digits
2892                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2893                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2894                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2895                         }
2896                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2897                             int depth=0; float score;
2898                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2899                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2900                                 pvInfoList[forwardMostMove-1].depth = depth;
2901                                 pvInfoList[forwardMostMove-1].score = 100*score;
2902                             }
2903                             OutputKibitz(suppressKibitz, parse);
2904                         } else {
2905                             char tmp[MSG_SIZ];
2906                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2907                             SendToPlayer(tmp, strlen(tmp));
2908                         }
2909                         next_out = i+1; // [HGM] suppress printing in ICS window
2910                     }
2911                     started = STARTED_NONE;
2912                 } else {
2913                     /* Don't match patterns against characters in comment */
2914                     i++;
2915                     continue;
2916                 }
2917             }
2918             if (started == STARTED_CHATTER) {
2919                 if (buf[i] != '\n') {
2920                     /* Don't match patterns against characters in chatter */
2921                     i++;
2922                     continue;
2923                 }
2924                 started = STARTED_NONE;
2925                 if(suppressKibitz) next_out = i+1;
2926             }
2927
2928             /* Kludge to deal with rcmd protocol */
2929             if (firstTime && looking_at(buf, &i, "\001*")) {
2930                 DisplayFatalError(&buf[1], 0, 1);
2931                 continue;
2932             } else {
2933                 firstTime = FALSE;
2934             }
2935
2936             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2937                 ics_type = ICS_ICC;
2938                 ics_prefix = "/";
2939                 if (appData.debugMode)
2940                   fprintf(debugFP, "ics_type %d\n", ics_type);
2941                 continue;
2942             }
2943             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2944                 ics_type = ICS_FICS;
2945                 ics_prefix = "$";
2946                 if (appData.debugMode)
2947                   fprintf(debugFP, "ics_type %d\n", ics_type);
2948                 continue;
2949             }
2950             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2951                 ics_type = ICS_CHESSNET;
2952                 ics_prefix = "/";
2953                 if (appData.debugMode)
2954                   fprintf(debugFP, "ics_type %d\n", ics_type);
2955                 continue;
2956             }
2957
2958             if (!loggedOn &&
2959                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2960                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2961                  looking_at(buf, &i, "will be \"*\""))) {
2962               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2963               continue;
2964             }
2965
2966             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2967               char buf[MSG_SIZ];
2968               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2969               DisplayIcsInteractionTitle(buf);
2970               have_set_title = TRUE;
2971             }
2972
2973             /* skip finger notes */
2974             if (started == STARTED_NONE &&
2975                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2976                  (buf[i] == '1' && buf[i+1] == '0')) &&
2977                 buf[i+2] == ':' && buf[i+3] == ' ') {
2978               started = STARTED_CHATTER;
2979               i += 3;
2980               continue;
2981             }
2982
2983             oldi = i;
2984             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2985             if(appData.seekGraph) {
2986                 if(soughtPending && MatchSoughtLine(buf+i)) {
2987                     i = strstr(buf+i, "rated") - buf;
2988                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2989                     next_out = leftover_start = i;
2990                     started = STARTED_CHATTER;
2991                     suppressKibitz = TRUE;
2992                     continue;
2993                 }
2994                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2995                         && looking_at(buf, &i, "* ads displayed")) {
2996                     soughtPending = FALSE;
2997                     seekGraphUp = TRUE;
2998                     DrawSeekGraph();
2999                     continue;
3000                 }
3001                 if(appData.autoRefresh) {
3002                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3003                         int s = (ics_type == ICS_ICC); // ICC format differs
3004                         if(seekGraphUp)
3005                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3006                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3007                         looking_at(buf, &i, "*% "); // eat prompt
3008                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3009                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3010                         next_out = i; // suppress
3011                         continue;
3012                     }
3013                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3014                         char *p = star_match[0];
3015                         while(*p) {
3016                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3017                             while(*p && *p++ != ' '); // next
3018                         }
3019                         looking_at(buf, &i, "*% "); // eat prompt
3020                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3021                         next_out = i;
3022                         continue;
3023                     }
3024                 }
3025             }
3026
3027             /* skip formula vars */
3028             if (started == STARTED_NONE &&
3029                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3030               started = STARTED_CHATTER;
3031               i += 3;
3032               continue;
3033             }
3034
3035             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3036             if (appData.autoKibitz && started == STARTED_NONE &&
3037                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3038                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3039                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3040                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3041                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3042                         suppressKibitz = TRUE;
3043                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                         next_out = i;
3045                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3046                                 && (gameMode == IcsPlayingWhite)) ||
3047                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3048                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3049                             started = STARTED_CHATTER; // own kibitz we simply discard
3050                         else {
3051                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3052                             parse_pos = 0; parse[0] = NULLCHAR;
3053                             savingComment = TRUE;
3054                             suppressKibitz = gameMode != IcsObserving ? 2 :
3055                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3056                         }
3057                         continue;
3058                 } else
3059                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3060                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3061                          && atoi(star_match[0])) {
3062                     // suppress the acknowledgements of our own autoKibitz
3063                     char *p;
3064                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3066                     SendToPlayer(star_match[0], strlen(star_match[0]));
3067                     if(looking_at(buf, &i, "*% ")) // eat prompt
3068                         suppressKibitz = FALSE;
3069                     next_out = i;
3070                     continue;
3071                 }
3072             } // [HGM] kibitz: end of patch
3073
3074             // [HGM] chat: intercept tells by users for which we have an open chat window
3075             channel = -1;
3076             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3077                                            looking_at(buf, &i, "* whispers:") ||
3078                                            looking_at(buf, &i, "* kibitzes:") ||
3079                                            looking_at(buf, &i, "* shouts:") ||
3080                                            looking_at(buf, &i, "* c-shouts:") ||
3081                                            looking_at(buf, &i, "--> * ") ||
3082                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3083                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3084                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3085                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3086                 int p;
3087                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3088                 chattingPartner = -1;
3089
3090                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3091                 for(p=0; p<MAX_CHAT; p++) {
3092                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3093                     talker[0] = '['; strcat(talker, "] ");
3094                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3095                     chattingPartner = p; break;
3096                     }
3097                 } else
3098                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3099                 for(p=0; p<MAX_CHAT; p++) {
3100                     if(!strcmp("kibitzes", chatPartner[p])) {
3101                         talker[0] = '['; strcat(talker, "] ");
3102                         chattingPartner = p; break;
3103                     }
3104                 } else
3105                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3106                 for(p=0; p<MAX_CHAT; p++) {
3107                     if(!strcmp("whispers", chatPartner[p])) {
3108                         talker[0] = '['; strcat(talker, "] ");
3109                         chattingPartner = p; break;
3110                     }
3111                 } else
3112                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3113                   if(buf[i-8] == '-' && buf[i-3] == 't')
3114                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3115                     if(!strcmp("c-shouts", chatPartner[p])) {
3116                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3117                         chattingPartner = p; break;
3118                     }
3119                   }
3120                   if(chattingPartner < 0)
3121                   for(p=0; p<MAX_CHAT; p++) {
3122                     if(!strcmp("shouts", chatPartner[p])) {
3123                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3124                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3125                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3126                         chattingPartner = p; break;
3127                     }
3128                   }
3129                 }
3130                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3131                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3132                     talker[0] = 0; Colorize(ColorTell, FALSE);
3133                     chattingPartner = p; break;
3134                 }
3135                 if(chattingPartner<0) i = oldi; else {
3136                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3137                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3138                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3139                     started = STARTED_COMMENT;
3140                     parse_pos = 0; parse[0] = NULLCHAR;
3141                     savingComment = 3 + chattingPartner; // counts as TRUE
3142                     suppressKibitz = TRUE;
3143                     continue;
3144                 }
3145             } // [HGM] chat: end of patch
3146
3147           backup = i;
3148             if (appData.zippyTalk || appData.zippyPlay) {
3149                 /* [DM] Backup address for color zippy lines */
3150 #if ZIPPY
3151                if (loggedOn == TRUE)
3152                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3153                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3154 #endif
3155             } // [DM] 'else { ' deleted
3156                 if (
3157                     /* Regular tells and says */
3158                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3159                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3160                     looking_at(buf, &i, "* says: ") ||
3161                     /* Don't color "message" or "messages" output */
3162                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3163                     looking_at(buf, &i, "*. * at *:*: ") ||
3164                     looking_at(buf, &i, "--* (*:*): ") ||
3165                     /* Message notifications (same color as tells) */
3166                     looking_at(buf, &i, "* has left a message ") ||
3167                     looking_at(buf, &i, "* just sent you a message:\n") ||
3168                     /* Whispers and kibitzes */
3169                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3170                     looking_at(buf, &i, "* kibitzes: ") ||
3171                     /* Channel tells */
3172                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3173
3174                   if (tkind == 1 && strchr(star_match[0], ':')) {
3175                       /* Avoid "tells you:" spoofs in channels */
3176                      tkind = 3;
3177                   }
3178                   if (star_match[0][0] == NULLCHAR ||
3179                       strchr(star_match[0], ' ') ||
3180                       (tkind == 3 && strchr(star_match[1], ' '))) {
3181                     /* Reject bogus matches */
3182                     i = oldi;
3183                   } else {
3184                     if (appData.colorize) {
3185                       if (oldi > next_out) {
3186                         SendToPlayer(&buf[next_out], oldi - next_out);
3187                         next_out = oldi;
3188                       }
3189                       switch (tkind) {
3190                       case 1:
3191                         Colorize(ColorTell, FALSE);
3192                         curColor = ColorTell;
3193                         break;
3194                       case 2:
3195                         Colorize(ColorKibitz, FALSE);
3196                         curColor = ColorKibitz;
3197                         break;
3198                       case 3:
3199                         p = strrchr(star_match[1], '(');
3200                         if (p == NULL) {
3201                           p = star_match[1];
3202                         } else {
3203                           p++;
3204                         }
3205                         if (atoi(p) == 1) {
3206                           Colorize(ColorChannel1, FALSE);
3207                           curColor = ColorChannel1;
3208                         } else {
3209                           Colorize(ColorChannel, FALSE);
3210                           curColor = ColorChannel;
3211                         }
3212                         break;
3213                       case 5:
3214                         curColor = ColorNormal;
3215                         break;
3216                       }
3217                     }
3218                     if (started == STARTED_NONE && appData.autoComment &&
3219                         (gameMode == IcsObserving ||
3220                          gameMode == IcsPlayingWhite ||
3221                          gameMode == IcsPlayingBlack)) {
3222                       parse_pos = i - oldi;
3223                       memcpy(parse, &buf[oldi], parse_pos);
3224                       parse[parse_pos] = NULLCHAR;
3225                       started = STARTED_COMMENT;
3226                       savingComment = TRUE;
3227                     } else {
3228                       started = STARTED_CHATTER;
3229                       savingComment = FALSE;
3230                     }
3231                     loggedOn = TRUE;
3232                     continue;
3233                   }
3234                 }
3235
3236                 if (looking_at(buf, &i, "* s-shouts: ") ||
3237                     looking_at(buf, &i, "* c-shouts: ")) {
3238                     if (appData.colorize) {
3239                         if (oldi > next_out) {
3240                             SendToPlayer(&buf[next_out], oldi - next_out);
3241                             next_out = oldi;
3242                         }
3243                         Colorize(ColorSShout, FALSE);
3244                         curColor = ColorSShout;
3245                     }
3246                     loggedOn = TRUE;
3247                     started = STARTED_CHATTER;
3248                     continue;
3249                 }
3250
3251                 if (looking_at(buf, &i, "--->")) {
3252                     loggedOn = TRUE;
3253                     continue;
3254                 }
3255
3256                 if (looking_at(buf, &i, "* shouts: ") ||
3257                     looking_at(buf, &i, "--> ")) {
3258                     if (appData.colorize) {
3259                         if (oldi > next_out) {
3260                             SendToPlayer(&buf[next_out], oldi - next_out);
3261                             next_out = oldi;
3262                         }
3263                         Colorize(ColorShout, FALSE);
3264                         curColor = ColorShout;
3265                     }
3266                     loggedOn = TRUE;
3267                     started = STARTED_CHATTER;
3268                     continue;
3269                 }
3270
3271                 if (looking_at( buf, &i, "Challenge:")) {
3272                     if (appData.colorize) {
3273                         if (oldi > next_out) {
3274                             SendToPlayer(&buf[next_out], oldi - next_out);
3275                             next_out = oldi;
3276                         }
3277                         Colorize(ColorChallenge, FALSE);
3278                         curColor = ColorChallenge;
3279                     }
3280                     loggedOn = TRUE;
3281                     continue;
3282                 }
3283
3284                 if (looking_at(buf, &i, "* offers you") ||
3285                     looking_at(buf, &i, "* offers to be") ||
3286                     looking_at(buf, &i, "* would like to") ||
3287                     looking_at(buf, &i, "* requests to") ||
3288                     looking_at(buf, &i, "Your opponent offers") ||
3289                     looking_at(buf, &i, "Your opponent requests")) {
3290
3291                     if (appData.colorize) {
3292                         if (oldi > next_out) {
3293                             SendToPlayer(&buf[next_out], oldi - next_out);
3294                             next_out = oldi;
3295                         }
3296                         Colorize(ColorRequest, FALSE);
3297                         curColor = ColorRequest;
3298                     }
3299                     continue;
3300                 }
3301
3302                 if (looking_at(buf, &i, "* (*) seeking")) {
3303                     if (appData.colorize) {
3304                         if (oldi > next_out) {
3305                             SendToPlayer(&buf[next_out], oldi - next_out);
3306                             next_out = oldi;
3307                         }
3308                         Colorize(ColorSeek, FALSE);
3309                         curColor = ColorSeek;
3310                     }
3311                     continue;
3312             }
3313
3314           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3315
3316             if (looking_at(buf, &i, "\\   ")) {
3317                 if (prevColor != ColorNormal) {
3318                     if (oldi > next_out) {
3319                         SendToPlayer(&buf[next_out], oldi - next_out);
3320                         next_out = oldi;
3321                     }
3322                     Colorize(prevColor, TRUE);
3323                     curColor = prevColor;
3324                 }
3325                 if (savingComment) {
3326                     parse_pos = i - oldi;
3327                     memcpy(parse, &buf[oldi], parse_pos);
3328                     parse[parse_pos] = NULLCHAR;
3329                     started = STARTED_COMMENT;
3330                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3331                         chattingPartner = savingComment - 3; // kludge to remember the box
3332                 } else {
3333                     started = STARTED_CHATTER;
3334                 }
3335                 continue;
3336             }
3337
3338             if (looking_at(buf, &i, "Black Strength :") ||
3339                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3340                 looking_at(buf, &i, "<10>") ||
3341                 looking_at(buf, &i, "#@#")) {
3342                 /* Wrong board style */
3343                 loggedOn = TRUE;
3344                 SendToICS(ics_prefix);
3345                 SendToICS("set style 12\n");
3346                 SendToICS(ics_prefix);
3347                 SendToICS("refresh\n");
3348                 continue;
3349             }
3350
3351             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3352                 ICSInitScript();
3353                 have_sent_ICS_logon = 1;
3354                 continue;
3355             }
3356
3357             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3358                 (looking_at(buf, &i, "\n<12> ") ||
3359                  looking_at(buf, &i, "<12> "))) {
3360                 loggedOn = TRUE;
3361                 if (oldi > next_out) {
3362                     SendToPlayer(&buf[next_out], oldi - next_out);
3363                 }
3364                 next_out = i;
3365                 started = STARTED_BOARD;
3366                 parse_pos = 0;
3367                 continue;
3368             }
3369
3370             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3371                 looking_at(buf, &i, "<b1> ")) {
3372                 if (oldi > next_out) {
3373                     SendToPlayer(&buf[next_out], oldi - next_out);
3374                 }
3375                 next_out = i;
3376                 started = STARTED_HOLDINGS;
3377                 parse_pos = 0;
3378                 continue;
3379             }
3380
3381             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3382                 loggedOn = TRUE;
3383                 /* Header for a move list -- first line */
3384
3385                 switch (ics_getting_history) {
3386                   case H_FALSE:
3387                     switch (gameMode) {
3388                       case IcsIdle:
3389                       case BeginningOfGame:
3390                         /* User typed "moves" or "oldmoves" while we
3391                            were idle.  Pretend we asked for these
3392                            moves and soak them up so user can step
3393                            through them and/or save them.
3394                            */
3395                         Reset(FALSE, TRUE);
3396                         gameMode = IcsObserving;
3397                         ModeHighlight();
3398                         ics_gamenum = -1;
3399                         ics_getting_history = H_GOT_UNREQ_HEADER;
3400                         break;
3401                       case EditGame: /*?*/
3402                       case EditPosition: /*?*/
3403                         /* Should above feature work in these modes too? */
3404                         /* For now it doesn't */
3405                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3406                         break;
3407                       default:
3408                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3409                         break;
3410                     }
3411                     break;
3412                   case H_REQUESTED:
3413                     /* Is this the right one? */
3414                     if (gameInfo.white && gameInfo.black &&
3415                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3416                         strcmp(gameInfo.black, star_match[2]) == 0) {
3417                         /* All is well */
3418                         ics_getting_history = H_GOT_REQ_HEADER;
3419                     }
3420                     break;
3421                   case H_GOT_REQ_HEADER:
3422                   case H_GOT_UNREQ_HEADER:
3423                   case H_GOT_UNWANTED_HEADER:
3424                   case H_GETTING_MOVES:
3425                     /* Should not happen */
3426                     DisplayError(_("Error gathering move list: two headers"), 0);
3427                     ics_getting_history = H_FALSE;
3428                     break;
3429                 }
3430
3431                 /* Save player ratings into gameInfo if needed */
3432                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3433                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3434                     (gameInfo.whiteRating == -1 ||
3435                      gameInfo.blackRating == -1)) {
3436
3437                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3438                     gameInfo.blackRating = string_to_rating(star_match[3]);
3439                     if (appData.debugMode)
3440                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3441                               gameInfo.whiteRating, gameInfo.blackRating);
3442                 }
3443                 continue;
3444             }
3445
3446             if (looking_at(buf, &i,
3447               "* * match, initial time: * minute*, increment: * second")) {
3448                 /* Header for a move list -- second line */
3449                 /* Initial board will follow if this is a wild game */
3450                 if (gameInfo.event != NULL) free(gameInfo.event);
3451                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3452                 gameInfo.event = StrSave(str);
3453                 /* [HGM] we switched variant. Translate boards if needed. */
3454                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3455                 continue;
3456             }
3457
3458             if (looking_at(buf, &i, "Move  ")) {
3459                 /* Beginning of a move list */
3460                 switch (ics_getting_history) {
3461                   case H_FALSE:
3462                     /* Normally should not happen */
3463                     /* Maybe user hit reset while we were parsing */
3464                     break;
3465                   case H_REQUESTED:
3466                     /* Happens if we are ignoring a move list that is not
3467                      * the one we just requested.  Common if the user
3468                      * tries to observe two games without turning off
3469                      * getMoveList */
3470                     break;
3471                   case H_GETTING_MOVES:
3472                     /* Should not happen */
3473                     DisplayError(_("Error gathering move list: nested"), 0);
3474                     ics_getting_history = H_FALSE;
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                     ics_getting_history = H_GETTING_MOVES;
3478                     started = STARTED_MOVES;
3479                     parse_pos = 0;
3480                     if (oldi > next_out) {
3481                         SendToPlayer(&buf[next_out], oldi - next_out);
3482                     }
3483                     break;
3484                   case H_GOT_UNREQ_HEADER:
3485                     ics_getting_history = H_GETTING_MOVES;
3486                     started = STARTED_MOVES_NOHIDE;
3487                     parse_pos = 0;
3488                     break;
3489                   case H_GOT_UNWANTED_HEADER:
3490                     ics_getting_history = H_FALSE;
3491                     break;
3492                 }
3493                 continue;
3494             }
3495
3496             if (looking_at(buf, &i, "% ") ||
3497                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3498                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3499                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3500                     soughtPending = FALSE;
3501                     seekGraphUp = TRUE;
3502                     DrawSeekGraph();
3503                 }
3504                 if(suppressKibitz) next_out = i;
3505                 savingComment = FALSE;
3506                 suppressKibitz = 0;
3507                 switch (started) {
3508                   case STARTED_MOVES:
3509                   case STARTED_MOVES_NOHIDE:
3510                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3511                     parse[parse_pos + i - oldi] = NULLCHAR;
3512                     ParseGameHistory(parse);
3513 #if ZIPPY
3514                     if (appData.zippyPlay && first.initDone) {
3515                         FeedMovesToProgram(&first, forwardMostMove);
3516                         if (gameMode == IcsPlayingWhite) {
3517                             if (WhiteOnMove(forwardMostMove)) {
3518                                 if (first.sendTime) {
3519                                   if (first.useColors) {
3520                                     SendToProgram("black\n", &first);
3521                                   }
3522                                   SendTimeRemaining(&first, TRUE);
3523                                 }
3524                                 if (first.useColors) {
3525                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3526                                 }
3527                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3528                                 first.maybeThinking = TRUE;
3529                             } else {
3530                                 if (first.usePlayother) {
3531                                   if (first.sendTime) {
3532                                     SendTimeRemaining(&first, TRUE);
3533                                   }
3534                                   SendToProgram("playother\n", &first);
3535                                   firstMove = FALSE;
3536                                 } else {
3537                                   firstMove = TRUE;
3538                                 }
3539                             }
3540                         } else if (gameMode == IcsPlayingBlack) {
3541                             if (!WhiteOnMove(forwardMostMove)) {
3542                                 if (first.sendTime) {
3543                                   if (first.useColors) {
3544                                     SendToProgram("white\n", &first);
3545                                   }
3546                                   SendTimeRemaining(&first, FALSE);
3547                                 }
3548                                 if (first.useColors) {
3549                                   SendToProgram("black\n", &first);
3550                                 }
3551                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3552                                 first.maybeThinking = TRUE;
3553                             } else {
3554                                 if (first.usePlayother) {
3555                                   if (first.sendTime) {
3556                                     SendTimeRemaining(&first, FALSE);
3557                                   }
3558                                   SendToProgram("playother\n", &first);
3559                                   firstMove = FALSE;
3560                                 } else {
3561                                   firstMove = TRUE;
3562                                 }
3563                             }
3564                         }
3565                     }
3566 #endif
3567                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3568                         /* Moves came from oldmoves or moves command
3569                            while we weren't doing anything else.
3570                            */
3571                         currentMove = forwardMostMove;
3572                         ClearHighlights();/*!!could figure this out*/
3573                         flipView = appData.flipView;
3574                         DrawPosition(TRUE, boards[currentMove]);
3575                         DisplayBothClocks();
3576                         snprintf(str, MSG_SIZ, "%s vs. %s",
3577                                 gameInfo.white, gameInfo.black);
3578                         DisplayTitle(str);
3579                         gameMode = IcsIdle;
3580                     } else {
3581                         /* Moves were history of an active game */
3582                         if (gameInfo.resultDetails != NULL) {
3583                             free(gameInfo.resultDetails);
3584                             gameInfo.resultDetails = NULL;
3585                         }
3586                     }
3587                     HistorySet(parseList, backwardMostMove,
3588                                forwardMostMove, currentMove-1);
3589                     DisplayMove(currentMove - 1);
3590                     if (started == STARTED_MOVES) next_out = i;
3591                     started = STARTED_NONE;
3592                     ics_getting_history = H_FALSE;
3593                     break;
3594
3595                   case STARTED_OBSERVE:
3596                     started = STARTED_NONE;
3597                     SendToICS(ics_prefix);
3598                     SendToICS("refresh\n");
3599                     break;
3600
3601                   default:
3602                     break;
3603                 }
3604                 if(bookHit) { // [HGM] book: simulate book reply
3605                     static char bookMove[MSG_SIZ]; // a bit generous?
3606
3607                     programStats.nodes = programStats.depth = programStats.time =
3608                     programStats.score = programStats.got_only_move = 0;
3609                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3610
3611                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3612                     strcat(bookMove, bookHit);
3613                     HandleMachineMove(bookMove, &first);
3614                 }
3615                 continue;
3616             }
3617
3618             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3619                  started == STARTED_HOLDINGS ||
3620                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3621                 /* Accumulate characters in move list or board */
3622                 parse[parse_pos++] = buf[i];
3623             }
3624
3625             /* Start of game messages.  Mostly we detect start of game
3626                when the first board image arrives.  On some versions
3627                of the ICS, though, we need to do a "refresh" after starting
3628                to observe in order to get the current board right away. */
3629             if (looking_at(buf, &i, "Adding game * to observation list")) {
3630                 started = STARTED_OBSERVE;
3631                 continue;
3632             }
3633
3634             /* Handle auto-observe */
3635             if (appData.autoObserve &&
3636                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3637                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3638                 char *player;
3639                 /* Choose the player that was highlighted, if any. */
3640                 if (star_match[0][0] == '\033' ||
3641                     star_match[1][0] != '\033') {
3642                     player = star_match[0];
3643                 } else {
3644                     player = star_match[2];
3645                 }
3646                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3647                         ics_prefix, StripHighlightAndTitle(player));
3648                 SendToICS(str);
3649
3650                 /* Save ratings from notify string */
3651                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3652                 player1Rating = string_to_rating(star_match[1]);
3653                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3654                 player2Rating = string_to_rating(star_match[3]);
3655
3656                 if (appData.debugMode)
3657                   fprintf(debugFP,
3658                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3659                           player1Name, player1Rating,
3660                           player2Name, player2Rating);
3661
3662                 continue;
3663             }
3664
3665             /* Deal with automatic examine mode after a game,
3666                and with IcsObserving -> IcsExamining transition */
3667             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3668                 looking_at(buf, &i, "has made you an examiner of game *")) {
3669
3670                 int gamenum = atoi(star_match[0]);
3671                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3672                     gamenum == ics_gamenum) {
3673                     /* We were already playing or observing this game;
3674                        no need to refetch history */
3675                     gameMode = IcsExamining;
3676                     if (pausing) {
3677                         pauseExamForwardMostMove = forwardMostMove;
3678                     } else if (currentMove < forwardMostMove) {
3679                         ForwardInner(forwardMostMove);
3680                     }
3681                 } else {
3682                     /* I don't think this case really can happen */
3683                     SendToICS(ics_prefix);
3684                     SendToICS("refresh\n");
3685                 }
3686                 continue;
3687             }
3688
3689             /* Error messages */
3690 //          if (ics_user_moved) {
3691             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3692                 if (looking_at(buf, &i, "Illegal move") ||
3693                     looking_at(buf, &i, "Not a legal move") ||
3694                     looking_at(buf, &i, "Your king is in check") ||
3695                     looking_at(buf, &i, "It isn't your turn") ||
3696                     looking_at(buf, &i, "It is not your move")) {
3697                     /* Illegal move */
3698                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3699                         currentMove = forwardMostMove-1;
3700                         DisplayMove(currentMove - 1); /* before DMError */
3701                         DrawPosition(FALSE, boards[currentMove]);
3702                         SwitchClocks(forwardMostMove-1); // [HGM] race
3703                         DisplayBothClocks();
3704                     }
3705                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3706                     ics_user_moved = 0;
3707                     continue;
3708                 }
3709             }
3710
3711             if (looking_at(buf, &i, "still have time") ||
3712                 looking_at(buf, &i, "not out of time") ||
3713                 looking_at(buf, &i, "either player is out of time") ||
3714                 looking_at(buf, &i, "has timeseal; checking")) {
3715                 /* We must have called his flag a little too soon */
3716                 whiteFlag = blackFlag = FALSE;
3717                 continue;
3718             }
3719
3720             if (looking_at(buf, &i, "added * seconds to") ||
3721                 looking_at(buf, &i, "seconds were added to")) {
3722                 /* Update the clocks */
3723                 SendToICS(ics_prefix);
3724                 SendToICS("refresh\n");
3725                 continue;
3726             }
3727
3728             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3729                 ics_clock_paused = TRUE;
3730                 StopClocks();
3731                 continue;
3732             }
3733
3734             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3735                 ics_clock_paused = FALSE;
3736                 StartClocks();
3737                 continue;
3738             }
3739
3740             /* Grab player ratings from the Creating: message.
3741                Note we have to check for the special case when
3742                the ICS inserts things like [white] or [black]. */
3743             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3744                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3745                 /* star_matches:
3746                    0    player 1 name (not necessarily white)
3747                    1    player 1 rating
3748                    2    empty, white, or black (IGNORED)
3749                    3    player 2 name (not necessarily black)
3750                    4    player 2 rating
3751
3752                    The names/ratings are sorted out when the game
3753                    actually starts (below).
3754                 */
3755                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3756                 player1Rating = string_to_rating(star_match[1]);
3757                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3758                 player2Rating = string_to_rating(star_match[4]);
3759
3760                 if (appData.debugMode)
3761                   fprintf(debugFP,
3762                           "Ratings from 'Creating:' %s %d, %s %d\n",
3763                           player1Name, player1Rating,
3764                           player2Name, player2Rating);
3765
3766                 continue;
3767             }
3768
3769             /* Improved generic start/end-of-game messages */
3770             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3771                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3772                 /* If tkind == 0: */
3773                 /* star_match[0] is the game number */
3774                 /*           [1] is the white player's name */
3775                 /*           [2] is the black player's name */
3776                 /* For end-of-game: */
3777                 /*           [3] is the reason for the game end */
3778                 /*           [4] is a PGN end game-token, preceded by " " */
3779                 /* For start-of-game: */
3780                 /*           [3] begins with "Creating" or "Continuing" */
3781                 /*           [4] is " *" or empty (don't care). */
3782                 int gamenum = atoi(star_match[0]);
3783                 char *whitename, *blackname, *why, *endtoken;
3784                 ChessMove endtype = EndOfFile;
3785
3786                 if (tkind == 0) {
3787                   whitename = star_match[1];
3788                   blackname = star_match[2];
3789                   why = star_match[3];
3790                   endtoken = star_match[4];
3791                 } else {
3792                   whitename = star_match[1];
3793                   blackname = star_match[3];
3794                   why = star_match[5];
3795                   endtoken = star_match[6];
3796                 }
3797
3798                 /* Game start messages */
3799                 if (strncmp(why, "Creating ", 9) == 0 ||
3800                     strncmp(why, "Continuing ", 11) == 0) {
3801                     gs_gamenum = gamenum;
3802                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3803                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3804 #if ZIPPY
3805                     if (appData.zippyPlay) {
3806                         ZippyGameStart(whitename, blackname);
3807                     }
3808 #endif /*ZIPPY*/
3809                     partnerBoardValid = FALSE; // [HGM] bughouse
3810                     continue;
3811                 }
3812
3813                 /* Game end messages */
3814                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3815                     ics_gamenum != gamenum) {
3816                     continue;
3817                 }
3818                 while (endtoken[0] == ' ') endtoken++;
3819                 switch (endtoken[0]) {
3820                   case '*':
3821                   default:
3822                     endtype = GameUnfinished;
3823                     break;
3824                   case '0':
3825                     endtype = BlackWins;
3826                     break;
3827                   case '1':
3828                     if (endtoken[1] == '/')
3829                       endtype = GameIsDrawn;
3830                     else
3831                       endtype = WhiteWins;
3832                     break;
3833                 }
3834                 GameEnds(endtype, why, GE_ICS);
3835 #if ZIPPY
3836                 if (appData.zippyPlay && first.initDone) {
3837                     ZippyGameEnd(endtype, why);
3838                     if (first.pr == NULL) {
3839                       /* Start the next process early so that we'll
3840                          be ready for the next challenge */
3841                       StartChessProgram(&first);
3842                     }
3843                     /* Send "new" early, in case this command takes
3844                        a long time to finish, so that we'll be ready
3845                        for the next challenge. */
3846                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3847                     Reset(TRUE, TRUE);
3848                 }
3849 #endif /*ZIPPY*/
3850                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3851                 continue;
3852             }
3853
3854             if (looking_at(buf, &i, "Removing game * from observation") ||
3855                 looking_at(buf, &i, "no longer observing game *") ||
3856                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3857                 if (gameMode == IcsObserving &&
3858                     atoi(star_match[0]) == ics_gamenum)
3859                   {
3860                       /* icsEngineAnalyze */
3861                       if (appData.icsEngineAnalyze) {
3862                             ExitAnalyzeMode();
3863                             ModeHighlight();
3864                       }
3865                       StopClocks();
3866                       gameMode = IcsIdle;
3867                       ics_gamenum = -1;
3868                       ics_user_moved = FALSE;
3869                   }
3870                 continue;
3871             }
3872
3873             if (looking_at(buf, &i, "no longer examining game *")) {
3874                 if (gameMode == IcsExamining &&
3875                     atoi(star_match[0]) == ics_gamenum)
3876                   {
3877                       gameMode = IcsIdle;
3878                       ics_gamenum = -1;
3879                       ics_user_moved = FALSE;
3880                   }
3881                 continue;
3882             }
3883
3884             /* Advance leftover_start past any newlines we find,
3885                so only partial lines can get reparsed */
3886             if (looking_at(buf, &i, "\n")) {
3887                 prevColor = curColor;
3888                 if (curColor != ColorNormal) {
3889                     if (oldi > next_out) {
3890                         SendToPlayer(&buf[next_out], oldi - next_out);
3891                         next_out = oldi;
3892                     }
3893                     Colorize(ColorNormal, FALSE);
3894                     curColor = ColorNormal;
3895                 }
3896                 if (started == STARTED_BOARD) {
3897                     started = STARTED_NONE;
3898                     parse[parse_pos] = NULLCHAR;
3899                     ParseBoard12(parse);
3900                     ics_user_moved = 0;
3901
3902                     /* Send premove here */
3903                     if (appData.premove) {
3904                       char str[MSG_SIZ];
3905                       if (currentMove == 0 &&
3906                           gameMode == IcsPlayingWhite &&
3907                           appData.premoveWhite) {
3908                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3909                         if (appData.debugMode)
3910                           fprintf(debugFP, "Sending premove:\n");
3911                         SendToICS(str);
3912                       } else if (currentMove == 1 &&
3913                                  gameMode == IcsPlayingBlack &&
3914                                  appData.premoveBlack) {
3915                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3916                         if (appData.debugMode)
3917                           fprintf(debugFP, "Sending premove:\n");
3918                         SendToICS(str);
3919                       } else if (gotPremove) {
3920                         gotPremove = 0;
3921                         ClearPremoveHighlights();
3922                         if (appData.debugMode)
3923                           fprintf(debugFP, "Sending premove:\n");
3924                           UserMoveEvent(premoveFromX, premoveFromY,
3925                                         premoveToX, premoveToY,
3926                                         premovePromoChar);
3927                       }
3928                     }
3929
3930                     /* Usually suppress following prompt */
3931                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3932                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3933                         if (looking_at(buf, &i, "*% ")) {
3934                             savingComment = FALSE;
3935                             suppressKibitz = 0;
3936                         }
3937                     }
3938                     next_out = i;
3939                 } else if (started == STARTED_HOLDINGS) {
3940                     int gamenum;
3941                     char new_piece[MSG_SIZ];
3942                     started = STARTED_NONE;
3943                     parse[parse_pos] = NULLCHAR;
3944                     if (appData.debugMode)
3945                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3946                                                         parse, currentMove);
3947                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3948                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3949                         if (gameInfo.variant == VariantNormal) {
3950                           /* [HGM] We seem to switch variant during a game!
3951                            * Presumably no holdings were displayed, so we have
3952                            * to move the position two files to the right to
3953                            * create room for them!
3954                            */
3955                           VariantClass newVariant;
3956                           switch(gameInfo.boardWidth) { // base guess on board width
3957                                 case 9:  newVariant = VariantShogi; break;
3958                                 case 10: newVariant = VariantGreat; break;
3959                                 default: newVariant = VariantCrazyhouse; break;
3960                           }
3961                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3962                           /* Get a move list just to see the header, which
3963                              will tell us whether this is really bug or zh */
3964                           if (ics_getting_history == H_FALSE) {
3965                             ics_getting_history = H_REQUESTED;
3966                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3967                             SendToICS(str);
3968                           }
3969                         }
3970                         new_piece[0] = NULLCHAR;
3971                         sscanf(parse, "game %d white [%s black [%s <- %s",
3972                                &gamenum, white_holding, black_holding,
3973                                new_piece);
3974                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3975                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3976                         /* [HGM] copy holdings to board holdings area */
3977                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3978                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3979                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3980 #if ZIPPY
3981                         if (appData.zippyPlay && first.initDone) {
3982                             ZippyHoldings(white_holding, black_holding,
3983                                           new_piece);
3984                         }
3985 #endif /*ZIPPY*/
3986                         if (tinyLayout || smallLayout) {
3987                             char wh[16], bh[16];
3988                             PackHolding(wh, white_holding);
3989                             PackHolding(bh, black_holding);
3990                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3991                                     gameInfo.white, gameInfo.black);
3992                         } else {
3993                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3994                                     gameInfo.white, white_holding,
3995                                     gameInfo.black, black_holding);
3996                         }
3997                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3998                         DrawPosition(FALSE, boards[currentMove]);
3999                         DisplayTitle(str);
4000                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4001                         sscanf(parse, "game %d white [%s black [%s <- %s",
4002                                &gamenum, white_holding, black_holding,
4003                                new_piece);
4004                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4005                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4006                         /* [HGM] copy holdings to partner-board holdings area */
4007                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4008                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4009                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4010                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4011                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4012                       }
4013                     }
4014                     /* Suppress following prompt */
4015                     if (looking_at(buf, &i, "*% ")) {
4016                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4017                         savingComment = FALSE;
4018                         suppressKibitz = 0;
4019                     }
4020                     next_out = i;
4021                 }
4022                 continue;
4023             }
4024
4025             i++;                /* skip unparsed character and loop back */
4026         }
4027
4028         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4029 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4030 //          SendToPlayer(&buf[next_out], i - next_out);
4031             started != STARTED_HOLDINGS && leftover_start > next_out) {
4032             SendToPlayer(&buf[next_out], leftover_start - next_out);
4033             next_out = i;
4034         }
4035
4036         leftover_len = buf_len - leftover_start;
4037         /* if buffer ends with something we couldn't parse,
4038            reparse it after appending the next read */
4039
4040     } else if (count == 0) {
4041         RemoveInputSource(isr);
4042         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4043     } else {
4044         DisplayFatalError(_("Error reading from ICS"), error, 1);
4045     }
4046 }
4047
4048
4049 /* Board style 12 looks like this:
4050
4051    <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
4052
4053  * The "<12> " is stripped before it gets to this routine.  The two
4054  * trailing 0's (flip state and clock ticking) are later addition, and
4055  * some chess servers may not have them, or may have only the first.
4056  * Additional trailing fields may be added in the future.
4057  */
4058
4059 #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"
4060
4061 #define RELATION_OBSERVING_PLAYED    0
4062 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4063 #define RELATION_PLAYING_MYMOVE      1
4064 #define RELATION_PLAYING_NOTMYMOVE  -1
4065 #define RELATION_EXAMINING           2
4066 #define RELATION_ISOLATED_BOARD     -3
4067 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4068
4069 void
4070 ParseBoard12(string)
4071      char *string;
4072 {
4073     GameMode newGameMode;
4074     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4075     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4076     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4077     char to_play, board_chars[200];
4078     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4079     char black[32], white[32];
4080     Board board;
4081     int prevMove = currentMove;
4082     int ticking = 2;
4083     ChessMove moveType;
4084     int fromX, fromY, toX, toY;
4085     char promoChar;
4086     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4087     char *bookHit = NULL; // [HGM] book
4088     Boolean weird = FALSE, reqFlag = FALSE;
4089
4090     fromX = fromY = toX = toY = -1;
4091
4092     newGame = FALSE;
4093
4094     if (appData.debugMode)
4095       fprintf(debugFP, _("Parsing board: %s\n"), string);
4096
4097     move_str[0] = NULLCHAR;
4098     elapsed_time[0] = NULLCHAR;
4099     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4100         int  i = 0, j;
4101         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4102             if(string[i] == ' ') { ranks++; files = 0; }
4103             else files++;
4104             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4105             i++;
4106         }
4107         for(j = 0; j <i; j++) board_chars[j] = string[j];
4108         board_chars[i] = '\0';
4109         string += i + 1;
4110     }
4111     n = sscanf(string, PATTERN, &to_play, &double_push,
4112                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4113                &gamenum, white, black, &relation, &basetime, &increment,
4114                &white_stren, &black_stren, &white_time, &black_time,
4115                &moveNum, str, elapsed_time, move_str, &ics_flip,
4116                &ticking);
4117
4118     if (n < 21) {
4119         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4120         DisplayError(str, 0);
4121         return;
4122     }
4123
4124     /* Convert the move number to internal form */
4125     moveNum = (moveNum - 1) * 2;
4126     if (to_play == 'B') moveNum++;
4127     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4128       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4129                         0, 1);
4130       return;
4131     }
4132
4133     switch (relation) {
4134       case RELATION_OBSERVING_PLAYED:
4135       case RELATION_OBSERVING_STATIC:
4136         if (gamenum == -1) {
4137             /* Old ICC buglet */
4138             relation = RELATION_OBSERVING_STATIC;
4139         }
4140         newGameMode = IcsObserving;
4141         break;
4142       case RELATION_PLAYING_MYMOVE:
4143       case RELATION_PLAYING_NOTMYMOVE:
4144         newGameMode =
4145           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4146             IcsPlayingWhite : IcsPlayingBlack;
4147         break;
4148       case RELATION_EXAMINING:
4149         newGameMode = IcsExamining;
4150         break;
4151       case RELATION_ISOLATED_BOARD:
4152       default:
4153         /* Just display this board.  If user was doing something else,
4154            we will forget about it until the next board comes. */
4155         newGameMode = IcsIdle;
4156         break;
4157       case RELATION_STARTING_POSITION:
4158         newGameMode = gameMode;
4159         break;
4160     }
4161
4162     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4163          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4164       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4165       char *toSqr;
4166       for (k = 0; k < ranks; k++) {
4167         for (j = 0; j < files; j++)
4168           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4169         if(gameInfo.holdingsWidth > 1) {
4170              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4171              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4172         }
4173       }
4174       CopyBoard(partnerBoard, board);
4175       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4176         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4177         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4178       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4179       if(toSqr = strchr(str, '-')) {
4180         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4181         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4182       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4183       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4184       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4185       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4186       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4187       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4188                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4189       DisplayMessage(partnerStatus, "");
4190         partnerBoardValid = TRUE;
4191       return;
4192     }
4193
4194     /* Modify behavior for initial board display on move listing
4195        of wild games.
4196        */
4197     switch (ics_getting_history) {
4198       case H_FALSE:
4199       case H_REQUESTED:
4200         break;
4201       case H_GOT_REQ_HEADER:
4202       case H_GOT_UNREQ_HEADER:
4203         /* This is the initial position of the current game */
4204         gamenum = ics_gamenum;
4205         moveNum = 0;            /* old ICS bug workaround */
4206         if (to_play == 'B') {
4207           startedFromSetupPosition = TRUE;
4208           blackPlaysFirst = TRUE;
4209           moveNum = 1;
4210           if (forwardMostMove == 0) forwardMostMove = 1;
4211           if (backwardMostMove == 0) backwardMostMove = 1;
4212           if (currentMove == 0) currentMove = 1;
4213         }
4214         newGameMode = gameMode;
4215         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4216         break;
4217       case H_GOT_UNWANTED_HEADER:
4218         /* This is an initial board that we don't want */
4219         return;
4220       case H_GETTING_MOVES:
4221         /* Should not happen */
4222         DisplayError(_("Error gathering move list: extra board"), 0);
4223         ics_getting_history = H_FALSE;
4224         return;
4225     }
4226
4227    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4228                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4229      /* [HGM] We seem to have switched variant unexpectedly
4230       * Try to guess new variant from board size
4231       */
4232           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4233           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4234           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4235           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4236           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4237           if(!weird) newVariant = VariantNormal;
4238           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4239           /* Get a move list just to see the header, which
4240              will tell us whether this is really bug or zh */
4241           if (ics_getting_history == H_FALSE) {
4242             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4243             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4244             SendToICS(str);
4245           }
4246     }
4247
4248     /* Take action if this is the first board of a new game, or of a
4249        different game than is currently being displayed.  */
4250     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4251         relation == RELATION_ISOLATED_BOARD) {
4252
4253         /* Forget the old game and get the history (if any) of the new one */
4254         if (gameMode != BeginningOfGame) {
4255           Reset(TRUE, TRUE);
4256         }
4257         newGame = TRUE;
4258         if (appData.autoRaiseBoard) BoardToTop();
4259         prevMove = -3;
4260         if (gamenum == -1) {
4261             newGameMode = IcsIdle;
4262         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4263                    appData.getMoveList && !reqFlag) {
4264             /* Need to get game history */
4265             ics_getting_history = H_REQUESTED;
4266             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4267             SendToICS(str);
4268         }
4269
4270         /* Initially flip the board to have black on the bottom if playing
4271            black or if the ICS flip flag is set, but let the user change
4272            it with the Flip View button. */
4273         flipView = appData.autoFlipView ?
4274           (newGameMode == IcsPlayingBlack) || ics_flip :
4275           appData.flipView;
4276
4277         /* Done with values from previous mode; copy in new ones */
4278         gameMode = newGameMode;
4279         ModeHighlight();
4280         ics_gamenum = gamenum;
4281         if (gamenum == gs_gamenum) {
4282             int klen = strlen(gs_kind);
4283             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4284             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4285             gameInfo.event = StrSave(str);
4286         } else {
4287             gameInfo.event = StrSave("ICS game");
4288         }
4289         gameInfo.site = StrSave(appData.icsHost);
4290         gameInfo.date = PGNDate();
4291         gameInfo.round = StrSave("-");
4292         gameInfo.white = StrSave(white);
4293         gameInfo.black = StrSave(black);
4294         timeControl = basetime * 60 * 1000;
4295         timeControl_2 = 0;
4296         timeIncrement = increment * 1000;
4297         movesPerSession = 0;
4298         gameInfo.timeControl = TimeControlTagValue();
4299         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4300   if (appData.debugMode) {
4301     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4302     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4303     setbuf(debugFP, NULL);
4304   }
4305
4306         gameInfo.outOfBook = NULL;
4307
4308         /* Do we have the ratings? */
4309         if (strcmp(player1Name, white) == 0 &&
4310             strcmp(player2Name, black) == 0) {
4311             if (appData.debugMode)
4312               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4313                       player1Rating, player2Rating);
4314             gameInfo.whiteRating = player1Rating;
4315             gameInfo.blackRating = player2Rating;
4316         } else if (strcmp(player2Name, white) == 0 &&
4317                    strcmp(player1Name, black) == 0) {
4318             if (appData.debugMode)
4319               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4320                       player2Rating, player1Rating);
4321             gameInfo.whiteRating = player2Rating;
4322             gameInfo.blackRating = player1Rating;
4323         }
4324         player1Name[0] = player2Name[0] = NULLCHAR;
4325
4326         /* Silence shouts if requested */
4327         if (appData.quietPlay &&
4328             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4329             SendToICS(ics_prefix);
4330             SendToICS("set shout 0\n");
4331         }
4332     }
4333
4334     /* Deal with midgame name changes */
4335     if (!newGame) {
4336         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4337             if (gameInfo.white) free(gameInfo.white);
4338             gameInfo.white = StrSave(white);
4339         }
4340         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4341             if (gameInfo.black) free(gameInfo.black);
4342             gameInfo.black = StrSave(black);
4343         }
4344     }
4345
4346     /* Throw away game result if anything actually changes in examine mode */
4347     if (gameMode == IcsExamining && !newGame) {
4348         gameInfo.result = GameUnfinished;
4349         if (gameInfo.resultDetails != NULL) {
4350             free(gameInfo.resultDetails);
4351             gameInfo.resultDetails = NULL;
4352         }
4353     }
4354
4355     /* In pausing && IcsExamining mode, we ignore boards coming
4356        in if they are in a different variation than we are. */
4357     if (pauseExamInvalid) return;
4358     if (pausing && gameMode == IcsExamining) {
4359         if (moveNum <= pauseExamForwardMostMove) {
4360             pauseExamInvalid = TRUE;
4361             forwardMostMove = pauseExamForwardMostMove;
4362             return;
4363         }
4364     }
4365
4366   if (appData.debugMode) {
4367     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4368   }
4369     /* Parse the board */
4370     for (k = 0; k < ranks; k++) {
4371       for (j = 0; j < files; j++)
4372         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4373       if(gameInfo.holdingsWidth > 1) {
4374            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4375            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4376       }
4377     }
4378     CopyBoard(boards[moveNum], board);
4379     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4380     if (moveNum == 0) {
4381         startedFromSetupPosition =
4382           !CompareBoards(board, initialPosition);
4383         if(startedFromSetupPosition)
4384             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4385     }
4386
4387     /* [HGM] Set castling rights. Take the outermost Rooks,
4388        to make it also work for FRC opening positions. Note that board12
4389        is really defective for later FRC positions, as it has no way to
4390        indicate which Rook can castle if they are on the same side of King.
4391        For the initial position we grant rights to the outermost Rooks,
4392        and remember thos rights, and we then copy them on positions
4393        later in an FRC game. This means WB might not recognize castlings with
4394        Rooks that have moved back to their original position as illegal,
4395        but in ICS mode that is not its job anyway.
4396     */
4397     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4398     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4399
4400         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4401             if(board[0][i] == WhiteRook) j = i;
4402         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4403         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4404             if(board[0][i] == WhiteRook) j = i;
4405         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4406         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4407             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4408         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4409         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4410             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4411         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412
4413         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4414         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4415             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4416         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4417             if(board[BOARD_HEIGHT-1][k] == bKing)
4418                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4419         if(gameInfo.variant == VariantTwoKings) {
4420             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4421             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4422             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4423         }
4424     } else { int r;
4425         r = boards[moveNum][CASTLING][0] = initialRights[0];
4426         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4427         r = boards[moveNum][CASTLING][1] = initialRights[1];
4428         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4429         r = boards[moveNum][CASTLING][3] = initialRights[3];
4430         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4431         r = boards[moveNum][CASTLING][4] = initialRights[4];
4432         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4433         /* wildcastle kludge: always assume King has rights */
4434         r = boards[moveNum][CASTLING][2] = initialRights[2];
4435         r = boards[moveNum][CASTLING][5] = initialRights[5];
4436     }
4437     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4438     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4439
4440
4441     if (ics_getting_history == H_GOT_REQ_HEADER ||
4442         ics_getting_history == H_GOT_UNREQ_HEADER) {
4443         /* This was an initial position from a move list, not
4444            the current position */
4445         return;
4446     }
4447
4448     /* Update currentMove and known move number limits */
4449     newMove = newGame || moveNum > forwardMostMove;
4450
4451     if (newGame) {
4452         forwardMostMove = backwardMostMove = currentMove = moveNum;
4453         if (gameMode == IcsExamining && moveNum == 0) {
4454           /* Workaround for ICS limitation: we are not told the wild
4455              type when starting to examine a game.  But if we ask for
4456              the move list, the move list header will tell us */
4457             ics_getting_history = H_REQUESTED;
4458             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4459             SendToICS(str);
4460         }
4461     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4462                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4463 #if ZIPPY
4464         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4465         /* [HGM] applied this also to an engine that is silently watching        */
4466         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4467             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4468             gameInfo.variant == currentlyInitializedVariant) {
4469           takeback = forwardMostMove - moveNum;
4470           for (i = 0; i < takeback; i++) {
4471             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4472             SendToProgram("undo\n", &first);
4473           }
4474         }
4475 #endif
4476
4477         forwardMostMove = moveNum;
4478         if (!pausing || currentMove > forwardMostMove)
4479           currentMove = forwardMostMove;
4480     } else {
4481         /* New part of history that is not contiguous with old part */
4482         if (pausing && gameMode == IcsExamining) {
4483             pauseExamInvalid = TRUE;
4484             forwardMostMove = pauseExamForwardMostMove;
4485             return;
4486         }
4487         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4488 #if ZIPPY
4489             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4490                 // [HGM] when we will receive the move list we now request, it will be
4491                 // fed to the engine from the first move on. So if the engine is not
4492                 // in the initial position now, bring it there.
4493                 InitChessProgram(&first, 0);
4494             }
4495 #endif
4496             ics_getting_history = H_REQUESTED;
4497             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4498             SendToICS(str);
4499         }
4500         forwardMostMove = backwardMostMove = currentMove = moveNum;
4501     }
4502
4503     /* Update the clocks */
4504     if (strchr(elapsed_time, '.')) {
4505       /* Time is in ms */
4506       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4507       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4508     } else {
4509       /* Time is in seconds */
4510       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4511       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4512     }
4513
4514
4515 #if ZIPPY
4516     if (appData.zippyPlay && newGame &&
4517         gameMode != IcsObserving && gameMode != IcsIdle &&
4518         gameMode != IcsExamining)
4519       ZippyFirstBoard(moveNum, basetime, increment);
4520 #endif
4521
4522     /* Put the move on the move list, first converting
4523        to canonical algebraic form. */
4524     if (moveNum > 0) {
4525   if (appData.debugMode) {
4526     if (appData.debugMode) { int f = forwardMostMove;
4527         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4528                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4529                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4530     }
4531     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4532     fprintf(debugFP, "moveNum = %d\n", moveNum);
4533     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4534     setbuf(debugFP, NULL);
4535   }
4536         if (moveNum <= backwardMostMove) {
4537             /* We don't know what the board looked like before
4538                this move.  Punt. */
4539           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4540             strcat(parseList[moveNum - 1], " ");
4541             strcat(parseList[moveNum - 1], elapsed_time);
4542             moveList[moveNum - 1][0] = NULLCHAR;
4543         } else if (strcmp(move_str, "none") == 0) {
4544             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4545             /* Again, we don't know what the board looked like;
4546                this is really the start of the game. */
4547             parseList[moveNum - 1][0] = NULLCHAR;
4548             moveList[moveNum - 1][0] = NULLCHAR;
4549             backwardMostMove = moveNum;
4550             startedFromSetupPosition = TRUE;
4551             fromX = fromY = toX = toY = -1;
4552         } else {
4553           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4554           //                 So we parse the long-algebraic move string in stead of the SAN move
4555           int valid; char buf[MSG_SIZ], *prom;
4556
4557           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4558                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4559           // str looks something like "Q/a1-a2"; kill the slash
4560           if(str[1] == '/')
4561             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4562           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4563           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4564                 strcat(buf, prom); // long move lacks promo specification!
4565           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4566                 if(appData.debugMode)
4567                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4568                 safeStrCpy(move_str, buf, MSG_SIZ);
4569           }
4570           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4571                                 &fromX, &fromY, &toX, &toY, &promoChar)
4572                || ParseOneMove(buf, moveNum - 1, &moveType,
4573                                 &fromX, &fromY, &toX, &toY, &promoChar);
4574           // end of long SAN patch
4575           if (valid) {
4576             (void) CoordsToAlgebraic(boards[moveNum - 1],
4577                                      PosFlags(moveNum - 1),
4578                                      fromY, fromX, toY, toX, promoChar,
4579                                      parseList[moveNum-1]);
4580             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4581               case MT_NONE:
4582               case MT_STALEMATE:
4583               default:
4584                 break;
4585               case MT_CHECK:
4586                 if(gameInfo.variant != VariantShogi)
4587                     strcat(parseList[moveNum - 1], "+");
4588                 break;
4589               case MT_CHECKMATE:
4590               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4591                 strcat(parseList[moveNum - 1], "#");
4592                 break;
4593             }
4594             strcat(parseList[moveNum - 1], " ");
4595             strcat(parseList[moveNum - 1], elapsed_time);
4596             /* currentMoveString is set as a side-effect of ParseOneMove */
4597             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4598             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4599             strcat(moveList[moveNum - 1], "\n");
4600
4601             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4602                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4603               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4604                 ChessSquare old, new = boards[moveNum][k][j];
4605                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4606                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4607                   if(old == new) continue;
4608                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4609                   else if(new == WhiteWazir || new == BlackWazir) {
4610                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4611                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4612                       else boards[moveNum][k][j] = old; // preserve type of Gold
4613                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4614                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4615               }
4616           } else {
4617             /* Move from ICS was illegal!?  Punt. */
4618             if (appData.debugMode) {
4619               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4620               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4621             }
4622             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4623             strcat(parseList[moveNum - 1], " ");
4624             strcat(parseList[moveNum - 1], elapsed_time);
4625             moveList[moveNum - 1][0] = NULLCHAR;
4626             fromX = fromY = toX = toY = -1;
4627           }
4628         }
4629   if (appData.debugMode) {
4630     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4631     setbuf(debugFP, NULL);
4632   }
4633
4634 #if ZIPPY
4635         /* Send move to chess program (BEFORE animating it). */
4636         if (appData.zippyPlay && !newGame && newMove &&
4637            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4638
4639             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4640                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4641                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4642                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4643                             move_str);
4644                     DisplayError(str, 0);
4645                 } else {
4646                     if (first.sendTime) {
4647                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4648                     }
4649                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4650                     if (firstMove && !bookHit) {
4651                         firstMove = FALSE;
4652                         if (first.useColors) {
4653                           SendToProgram(gameMode == IcsPlayingWhite ?
4654                                         "white\ngo\n" :
4655                                         "black\ngo\n", &first);
4656                         } else {
4657                           SendToProgram("go\n", &first);
4658                         }
4659                         first.maybeThinking = TRUE;
4660                     }
4661                 }
4662             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4663               if (moveList[moveNum - 1][0] == NULLCHAR) {
4664                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4665                 DisplayError(str, 0);
4666               } else {
4667                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4668                 SendMoveToProgram(moveNum - 1, &first);
4669               }
4670             }
4671         }
4672 #endif
4673     }
4674
4675     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4676         /* If move comes from a remote source, animate it.  If it
4677            isn't remote, it will have already been animated. */
4678         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4679             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4680         }
4681         if (!pausing && appData.highlightLastMove) {
4682             SetHighlights(fromX, fromY, toX, toY);
4683         }
4684     }
4685
4686     /* Start the clocks */
4687     whiteFlag = blackFlag = FALSE;
4688     appData.clockMode = !(basetime == 0 && increment == 0);
4689     if (ticking == 0) {
4690       ics_clock_paused = TRUE;
4691       StopClocks();
4692     } else if (ticking == 1) {
4693       ics_clock_paused = FALSE;
4694     }
4695     if (gameMode == IcsIdle ||
4696         relation == RELATION_OBSERVING_STATIC ||
4697         relation == RELATION_EXAMINING ||
4698         ics_clock_paused)
4699       DisplayBothClocks();
4700     else
4701       StartClocks();
4702
4703     /* Display opponents and material strengths */
4704     if (gameInfo.variant != VariantBughouse &&
4705         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4706         if (tinyLayout || smallLayout) {
4707             if(gameInfo.variant == VariantNormal)
4708               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4709                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4710                     basetime, increment);
4711             else
4712               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4713                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4714                     basetime, increment, (int) gameInfo.variant);
4715         } else {
4716             if(gameInfo.variant == VariantNormal)
4717               snprintf(str, MSG_SIZ, "%s (%d) vs. %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) vs. %s (%d) {%d %d %s}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment, VariantName(gameInfo.variant));
4724         }
4725         DisplayTitle(str);
4726   if (appData.debugMode) {
4727     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4728   }
4729     }
4730
4731
4732     /* Display the board */
4733     if (!pausing && !appData.noGUI) {
4734
4735       if (appData.premove)
4736           if (!gotPremove ||
4737              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4738              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4739               ClearPremoveHighlights();
4740
4741       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4742         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4743       DrawPosition(j, boards[currentMove]);
4744
4745       DisplayMove(moveNum - 1);
4746       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4747             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4748               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4749         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4750       }
4751     }
4752
4753     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4754 #if ZIPPY
4755     if(bookHit) { // [HGM] book: simulate book reply
4756         static char bookMove[MSG_SIZ]; // a bit generous?
4757
4758         programStats.nodes = programStats.depth = programStats.time =
4759         programStats.score = programStats.got_only_move = 0;
4760         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4761
4762         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4763         strcat(bookMove, bookHit);
4764         HandleMachineMove(bookMove, &first);
4765     }
4766 #endif
4767 }
4768
4769 void
4770 GetMoveListEvent()
4771 {
4772     char buf[MSG_SIZ];
4773     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4774         ics_getting_history = H_REQUESTED;
4775         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4776         SendToICS(buf);
4777     }
4778 }
4779
4780 void
4781 AnalysisPeriodicEvent(force)
4782      int force;
4783 {
4784     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4785          && !force) || !appData.periodicUpdates)
4786       return;
4787
4788     /* Send . command to Crafty to collect stats */
4789     SendToProgram(".\n", &first);
4790
4791     /* Don't send another until we get a response (this makes
4792        us stop sending to old Crafty's which don't understand
4793        the "." command (sending illegal cmds resets node count & time,
4794        which looks bad)) */
4795     programStats.ok_to_send = 0;
4796 }
4797
4798 void ics_update_width(new_width)
4799         int new_width;
4800 {
4801         ics_printf("set width %d\n", new_width);
4802 }
4803
4804 void
4805 SendMoveToProgram(moveNum, cps)
4806      int moveNum;
4807      ChessProgramState *cps;
4808 {
4809     char buf[MSG_SIZ];
4810
4811     if (cps->useUsermove) {
4812       SendToProgram("usermove ", cps);
4813     }
4814     if (cps->useSAN) {
4815       char *space;
4816       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4817         int len = space - parseList[moveNum];
4818         memcpy(buf, parseList[moveNum], len);
4819         buf[len++] = '\n';
4820         buf[len] = NULLCHAR;
4821       } else {
4822         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4823       }
4824       SendToProgram(buf, cps);
4825     } else {
4826       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4827         AlphaRank(moveList[moveNum], 4);
4828         SendToProgram(moveList[moveNum], cps);
4829         AlphaRank(moveList[moveNum], 4); // and back
4830       } else
4831       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4832        * the engine. It would be nice to have a better way to identify castle
4833        * moves here. */
4834       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4835                                                                          && cps->useOOCastle) {
4836         int fromX = moveList[moveNum][0] - AAA;
4837         int fromY = moveList[moveNum][1] - ONE;
4838         int toX = moveList[moveNum][2] - AAA;
4839         int toY = moveList[moveNum][3] - ONE;
4840         if((boards[moveNum][fromY][fromX] == WhiteKing
4841             && boards[moveNum][toY][toX] == WhiteRook)
4842            || (boards[moveNum][fromY][fromX] == BlackKing
4843                && boards[moveNum][toY][toX] == BlackRook)) {
4844           if(toX > fromX) SendToProgram("O-O\n", cps);
4845           else SendToProgram("O-O-O\n", cps);
4846         }
4847         else SendToProgram(moveList[moveNum], cps);
4848       }
4849       else SendToProgram(moveList[moveNum], cps);
4850       /* End of additions by Tord */
4851     }
4852
4853     /* [HGM] setting up the opening has brought engine in force mode! */
4854     /*       Send 'go' if we are in a mode where machine should play. */
4855     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4856         (gameMode == TwoMachinesPlay   ||
4857 #if ZIPPY
4858          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4859 #endif
4860          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4861         SendToProgram("go\n", cps);
4862   if (appData.debugMode) {
4863     fprintf(debugFP, "(extra)\n");
4864   }
4865     }
4866     setboardSpoiledMachineBlack = 0;
4867 }
4868
4869 void
4870 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4871      ChessMove moveType;
4872      int fromX, fromY, toX, toY;
4873      char promoChar;
4874 {
4875     char user_move[MSG_SIZ];
4876
4877     switch (moveType) {
4878       default:
4879         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4880                 (int)moveType, fromX, fromY, toX, toY);
4881         DisplayError(user_move + strlen("say "), 0);
4882         break;
4883       case WhiteKingSideCastle:
4884       case BlackKingSideCastle:
4885       case WhiteQueenSideCastleWild:
4886       case BlackQueenSideCastleWild:
4887       /* PUSH Fabien */
4888       case WhiteHSideCastleFR:
4889       case BlackHSideCastleFR:
4890       /* POP Fabien */
4891         snprintf(user_move, MSG_SIZ, "o-o\n");
4892         break;
4893       case WhiteQueenSideCastle:
4894       case BlackQueenSideCastle:
4895       case WhiteKingSideCastleWild:
4896       case BlackKingSideCastleWild:
4897       /* PUSH Fabien */
4898       case WhiteASideCastleFR:
4899       case BlackASideCastleFR:
4900       /* POP Fabien */
4901         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4902         break;
4903       case WhiteNonPromotion:
4904       case BlackNonPromotion:
4905         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4906         break;
4907       case WhitePromotion:
4908       case BlackPromotion:
4909         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4910           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4911                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4912                 PieceToChar(WhiteFerz));
4913         else if(gameInfo.variant == VariantGreat)
4914           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4915                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4916                 PieceToChar(WhiteMan));
4917         else
4918           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4919                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4920                 promoChar);
4921         break;
4922       case WhiteDrop:
4923       case BlackDrop:
4924       drop:
4925         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4926                  ToUpper(PieceToChar((ChessSquare) fromX)),
4927                  AAA + toX, ONE + toY);
4928         break;
4929       case IllegalMove:  /* could be a variant we don't quite understand */
4930         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4931       case NormalMove:
4932       case WhiteCapturesEnPassant:
4933       case BlackCapturesEnPassant:
4934         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4935                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4936         break;
4937     }
4938     SendToICS(user_move);
4939     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4940         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4941 }
4942
4943 void
4944 UploadGameEvent()
4945 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4946     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4947     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4948     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4949         DisplayError("You cannot do this while you are playing or observing", 0);
4950         return;
4951     }
4952     if(gameMode != IcsExamining) { // is this ever not the case?
4953         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4954
4955         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4956           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4957         } else { // on FICS we must first go to general examine mode
4958           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4959         }
4960         if(gameInfo.variant != VariantNormal) {
4961             // try figure out wild number, as xboard names are not always valid on ICS
4962             for(i=1; i<=36; i++) {
4963               snprintf(buf, MSG_SIZ, "wild/%d", i);
4964                 if(StringToVariant(buf) == gameInfo.variant) break;
4965             }
4966             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4967             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4968             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4969         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4970         SendToICS(ics_prefix);
4971         SendToICS(buf);
4972         if(startedFromSetupPosition || backwardMostMove != 0) {
4973           fen = PositionToFEN(backwardMostMove, NULL);
4974           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4975             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4976             SendToICS(buf);
4977           } else { // FICS: everything has to set by separate bsetup commands
4978             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4979             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4980             SendToICS(buf);
4981             if(!WhiteOnMove(backwardMostMove)) {
4982                 SendToICS("bsetup tomove black\n");
4983             }
4984             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4985             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4986             SendToICS(buf);
4987             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4988             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4989             SendToICS(buf);
4990             i = boards[backwardMostMove][EP_STATUS];
4991             if(i >= 0) { // set e.p.
4992               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4993                 SendToICS(buf);
4994             }
4995             bsetup++;
4996           }
4997         }
4998       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4999             SendToICS("bsetup done\n"); // switch to normal examining.
5000     }
5001     for(i = backwardMostMove; i<last; i++) {
5002         char buf[20];
5003         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5004         SendToICS(buf);
5005     }
5006     SendToICS(ics_prefix);
5007     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5008 }
5009
5010 void
5011 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5012      int rf, ff, rt, ft;
5013      char promoChar;
5014      char move[7];
5015 {
5016     if (rf == DROP_RANK) {
5017       sprintf(move, "%c@%c%c\n",
5018                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5019     } else {
5020         if (promoChar == 'x' || promoChar == NULLCHAR) {
5021           sprintf(move, "%c%c%c%c\n",
5022                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5023         } else {
5024             sprintf(move, "%c%c%c%c%c\n",
5025                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5026         }
5027     }
5028 }
5029
5030 void
5031 ProcessICSInitScript(f)
5032      FILE *f;
5033 {
5034     char buf[MSG_SIZ];
5035
5036     while (fgets(buf, MSG_SIZ, f)) {
5037         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5038     }
5039
5040     fclose(f);
5041 }
5042
5043
5044 static int lastX, lastY, selectFlag, dragging;
5045
5046 void
5047 Sweep(int step)
5048 {
5049     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5050     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5051     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5052     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5053     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5054     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5055     do {
5056         promoSweep -= step;
5057         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5058         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5059         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5060         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5061         if(!step) step = 1;
5062     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5063             appData.testLegality && (promoSweep == king ||
5064             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5065     ChangeDragPiece(promoSweep);
5066 }
5067
5068 int PromoScroll(int x, int y)
5069 {
5070   int step = 0;
5071
5072   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5073   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5074   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5075   if(!step) return FALSE;
5076   lastX = x; lastY = y;
5077   if((promoSweep < BlackPawn) == flipView) step = -step;
5078   if(step > 0) selectFlag = 1;
5079   if(!selectFlag) Sweep(step);
5080   return FALSE;
5081 }
5082
5083 void
5084 NextPiece(int step)
5085 {
5086     ChessSquare piece = boards[currentMove][toY][toX];
5087     do {
5088         pieceSweep -= step;
5089         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5090         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5091         if(!step) step = -1;
5092     } while(PieceToChar(pieceSweep) == '.');
5093     boards[currentMove][toY][toX] = pieceSweep;
5094     DrawPosition(FALSE, boards[currentMove]);
5095     boards[currentMove][toY][toX] = piece;
5096 }
5097 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5098 void
5099 AlphaRank(char *move, int n)
5100 {
5101 //    char *p = move, c; int x, y;
5102
5103     if (appData.debugMode) {
5104         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5105     }
5106
5107     if(move[1]=='*' &&
5108        move[2]>='0' && move[2]<='9' &&
5109        move[3]>='a' && move[3]<='x'    ) {
5110         move[1] = '@';
5111         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5112         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5113     } else
5114     if(move[0]>='0' && move[0]<='9' &&
5115        move[1]>='a' && move[1]<='x' &&
5116        move[2]>='0' && move[2]<='9' &&
5117        move[3]>='a' && move[3]<='x'    ) {
5118         /* input move, Shogi -> normal */
5119         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5120         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5121         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5122         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5123     } else
5124     if(move[1]=='@' &&
5125        move[3]>='0' && move[3]<='9' &&
5126        move[2]>='a' && move[2]<='x'    ) {
5127         move[1] = '*';
5128         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5129         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5130     } else
5131     if(
5132        move[0]>='a' && move[0]<='x' &&
5133        move[3]>='0' && move[3]<='9' &&
5134        move[2]>='a' && move[2]<='x'    ) {
5135          /* output move, normal -> Shogi */
5136         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5137         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5138         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5139         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5140         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5141     }
5142     if (appData.debugMode) {
5143         fprintf(debugFP, "   out = '%s'\n", move);
5144     }
5145 }
5146
5147 char yy_textstr[8000];
5148
5149 /* Parser for moves from gnuchess, ICS, or user typein box */
5150 Boolean
5151 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5152      char *move;
5153      int moveNum;
5154      ChessMove *moveType;
5155      int *fromX, *fromY, *toX, *toY;
5156      char *promoChar;
5157 {
5158     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5159
5160     switch (*moveType) {
5161       case WhitePromotion:
5162       case BlackPromotion:
5163       case WhiteNonPromotion:
5164       case BlackNonPromotion:
5165       case NormalMove:
5166       case WhiteCapturesEnPassant:
5167       case BlackCapturesEnPassant:
5168       case WhiteKingSideCastle:
5169       case WhiteQueenSideCastle:
5170       case BlackKingSideCastle:
5171       case BlackQueenSideCastle:
5172       case WhiteKingSideCastleWild:
5173       case WhiteQueenSideCastleWild:
5174       case BlackKingSideCastleWild:
5175       case BlackQueenSideCastleWild:
5176       /* Code added by Tord: */
5177       case WhiteHSideCastleFR:
5178       case WhiteASideCastleFR:
5179       case BlackHSideCastleFR:
5180       case BlackASideCastleFR:
5181       /* End of code added by Tord */
5182       case IllegalMove:         /* bug or odd chess variant */
5183         *fromX = currentMoveString[0] - AAA;
5184         *fromY = currentMoveString[1] - ONE;
5185         *toX = currentMoveString[2] - AAA;
5186         *toY = currentMoveString[3] - ONE;
5187         *promoChar = currentMoveString[4];
5188         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5189             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5190     if (appData.debugMode) {
5191         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5192     }
5193             *fromX = *fromY = *toX = *toY = 0;
5194             return FALSE;
5195         }
5196         if (appData.testLegality) {
5197           return (*moveType != IllegalMove);
5198         } else {
5199           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5200                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5201         }
5202
5203       case WhiteDrop:
5204       case BlackDrop:
5205         *fromX = *moveType == WhiteDrop ?
5206           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5207           (int) CharToPiece(ToLower(currentMoveString[0]));
5208         *fromY = DROP_RANK;
5209         *toX = currentMoveString[2] - AAA;
5210         *toY = currentMoveString[3] - ONE;
5211         *promoChar = NULLCHAR;
5212         return TRUE;
5213
5214       case AmbiguousMove:
5215       case ImpossibleMove:
5216       case EndOfFile:
5217       case ElapsedTime:
5218       case Comment:
5219       case PGNTag:
5220       case NAG:
5221       case WhiteWins:
5222       case BlackWins:
5223       case GameIsDrawn:
5224       default:
5225     if (appData.debugMode) {
5226         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5227     }
5228         /* bug? */
5229         *fromX = *fromY = *toX = *toY = 0;
5230         *promoChar = NULLCHAR;
5231         return FALSE;
5232     }
5233 }
5234
5235 Boolean pushed = FALSE;
5236
5237 void
5238 ParsePV(char *pv, Boolean storeComments)
5239 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5240   int fromX, fromY, toX, toY; char promoChar;
5241   ChessMove moveType;
5242   Boolean valid;
5243   int nr = 0;
5244
5245   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5246     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5247     pushed = TRUE;
5248   }
5249   endPV = forwardMostMove;
5250   do {
5251     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5252     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5253     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5254 if(appData.debugMode){
5255 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);
5256 }
5257     if(!valid && nr == 0 &&
5258        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5259         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5260         // Hande case where played move is different from leading PV move
5261         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5262         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5263         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5264         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5265           endPV += 2; // if position different, keep this
5266           moveList[endPV-1][0] = fromX + AAA;
5267           moveList[endPV-1][1] = fromY + ONE;
5268           moveList[endPV-1][2] = toX + AAA;
5269           moveList[endPV-1][3] = toY + ONE;
5270           parseList[endPV-1][0] = NULLCHAR;
5271           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5272         }
5273       }
5274     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5275     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5276     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5277     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5278         valid++; // allow comments in PV
5279         continue;
5280     }
5281     nr++;
5282     if(endPV+1 > framePtr) break; // no space, truncate
5283     if(!valid) break;
5284     endPV++;
5285     CopyBoard(boards[endPV], boards[endPV-1]);
5286     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5287     moveList[endPV-1][0] = fromX + AAA;
5288     moveList[endPV-1][1] = fromY + ONE;
5289     moveList[endPV-1][2] = toX + AAA;
5290     moveList[endPV-1][3] = toY + ONE;
5291     moveList[endPV-1][4] = promoChar;
5292     moveList[endPV-1][5] = NULLCHAR;
5293     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5294     if(storeComments)
5295         CoordsToAlgebraic(boards[endPV - 1],
5296                              PosFlags(endPV - 1),
5297                              fromY, fromX, toY, toX, promoChar,
5298                              parseList[endPV - 1]);
5299     else
5300         parseList[endPV-1][0] = NULLCHAR;
5301   } while(valid);
5302   currentMove = endPV;
5303   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5304   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5305                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5306   DrawPosition(TRUE, boards[currentMove]);
5307 }
5308
5309 Boolean
5310 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5311 {
5312         int startPV;
5313         char *p;
5314
5315         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5316         lastX = x; lastY = y;
5317         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5318         startPV = index;
5319         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5320         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5321         index = startPV;
5322         do{ while(buf[index] && buf[index] != '\n') index++;
5323         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5324         buf[index] = 0;
5325         ParsePV(buf+startPV, FALSE);
5326         *start = startPV; *end = index-1;
5327         return TRUE;
5328 }
5329
5330 Boolean
5331 LoadPV(int x, int y)
5332 { // called on right mouse click to load PV
5333   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5334   lastX = x; lastY = y;
5335   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5336   return TRUE;
5337 }
5338
5339 void
5340 UnLoadPV()
5341 {
5342   if(endPV < 0) return;
5343   endPV = -1;
5344   currentMove = forwardMostMove;
5345   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5346   ClearPremoveHighlights();
5347   DrawPosition(TRUE, boards[currentMove]);
5348 }
5349
5350 void
5351 MovePV(int x, int y, int h)
5352 { // step through PV based on mouse coordinates (called on mouse move)
5353   int margin = h>>3, step = 0;
5354
5355   // we must somehow check if right button is still down (might be released off board!)
5356   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5357   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5358   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5359   if(!step) return;
5360   lastX = x; lastY = y;
5361
5362   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5363   if(endPV < 0) return;
5364   if(y < margin) step = 1; else
5365   if(y > h - margin) step = -1;
5366   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5367   currentMove += step;
5368   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5369   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5370                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5371   DrawPosition(FALSE, boards[currentMove]);
5372 }
5373
5374
5375 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5376 // All positions will have equal probability, but the current method will not provide a unique
5377 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5378 #define DARK 1
5379 #define LITE 2
5380 #define ANY 3
5381
5382 int squaresLeft[4];
5383 int piecesLeft[(int)BlackPawn];
5384 int seed, nrOfShuffles;
5385
5386 void GetPositionNumber()
5387 {       // sets global variable seed
5388         int i;
5389
5390         seed = appData.defaultFrcPosition;
5391         if(seed < 0) { // randomize based on time for negative FRC position numbers
5392                 for(i=0; i<50; i++) seed += random();
5393                 seed = random() ^ random() >> 8 ^ random() << 8;
5394                 if(seed<0) seed = -seed;
5395         }
5396 }
5397
5398 int put(Board board, int pieceType, int rank, int n, int shade)
5399 // put the piece on the (n-1)-th empty squares of the given shade
5400 {
5401         int i;
5402
5403         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5404                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5405                         board[rank][i] = (ChessSquare) pieceType;
5406                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5407                         squaresLeft[ANY]--;
5408                         piecesLeft[pieceType]--;
5409                         return i;
5410                 }
5411         }
5412         return -1;
5413 }
5414
5415
5416 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5417 // calculate where the next piece goes, (any empty square), and put it there
5418 {
5419         int i;
5420
5421         i = seed % squaresLeft[shade];
5422         nrOfShuffles *= squaresLeft[shade];
5423         seed /= squaresLeft[shade];
5424         put(board, pieceType, rank, i, shade);
5425 }
5426
5427 void AddTwoPieces(Board board, int pieceType, int rank)
5428 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5429 {
5430         int i, n=squaresLeft[ANY], j=n-1, k;
5431
5432         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5433         i = seed % k;  // pick one
5434         nrOfShuffles *= k;
5435         seed /= k;
5436         while(i >= j) i -= j--;
5437         j = n - 1 - j; i += j;
5438         put(board, pieceType, rank, j, ANY);
5439         put(board, pieceType, rank, i, ANY);
5440 }
5441
5442 void SetUpShuffle(Board board, int number)
5443 {
5444         int i, p, first=1;
5445
5446         GetPositionNumber(); nrOfShuffles = 1;
5447
5448         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5449         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5450         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5451
5452         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5453
5454         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5455             p = (int) board[0][i];
5456             if(p < (int) BlackPawn) piecesLeft[p] ++;
5457             board[0][i] = EmptySquare;
5458         }
5459
5460         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5461             // shuffles restricted to allow normal castling put KRR first
5462             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5463                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5464             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5465                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5466             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5467                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5468             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5469                 put(board, WhiteRook, 0, 0, ANY);
5470             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5471         }
5472
5473         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5474             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5475             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5476                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5477                 while(piecesLeft[p] >= 2) {
5478                     AddOnePiece(board, p, 0, LITE);
5479                     AddOnePiece(board, p, 0, DARK);
5480                 }
5481                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5482             }
5483
5484         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5485             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5486             // but we leave King and Rooks for last, to possibly obey FRC restriction
5487             if(p == (int)WhiteRook) continue;
5488             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5489             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5490         }
5491
5492         // now everything is placed, except perhaps King (Unicorn) and Rooks
5493
5494         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5495             // Last King gets castling rights
5496             while(piecesLeft[(int)WhiteUnicorn]) {
5497                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5498                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5499             }
5500
5501             while(piecesLeft[(int)WhiteKing]) {
5502                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5503                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5504             }
5505
5506
5507         } else {
5508             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5509             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5510         }
5511
5512         // Only Rooks can be left; simply place them all
5513         while(piecesLeft[(int)WhiteRook]) {
5514                 i = put(board, WhiteRook, 0, 0, ANY);
5515                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5516                         if(first) {
5517                                 first=0;
5518                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5519                         }
5520                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5521                 }
5522         }
5523         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5524             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5525         }
5526
5527         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5528 }
5529
5530 int SetCharTable( char *table, const char * map )
5531 /* [HGM] moved here from winboard.c because of its general usefulness */
5532 /*       Basically a safe strcpy that uses the last character as King */
5533 {
5534     int result = FALSE; int NrPieces;
5535
5536     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5537                     && NrPieces >= 12 && !(NrPieces&1)) {
5538         int i; /* [HGM] Accept even length from 12 to 34 */
5539
5540         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5541         for( i=0; i<NrPieces/2-1; i++ ) {
5542             table[i] = map[i];
5543             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5544         }
5545         table[(int) WhiteKing]  = map[NrPieces/2-1];
5546         table[(int) BlackKing]  = map[NrPieces-1];
5547
5548         result = TRUE;
5549     }
5550
5551     return result;
5552 }
5553
5554 void Prelude(Board board)
5555 {       // [HGM] superchess: random selection of exo-pieces
5556         int i, j, k; ChessSquare p;
5557         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5558
5559         GetPositionNumber(); // use FRC position number
5560
5561         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5562             SetCharTable(pieceToChar, appData.pieceToCharTable);
5563             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5564                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5565         }
5566
5567         j = seed%4;                 seed /= 4;
5568         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5569         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5570         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5571         j = seed%3 + (seed%3 >= j); seed /= 3;
5572         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5573         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5574         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5575         j = seed%3;                 seed /= 3;
5576         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5577         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5578         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5579         j = seed%2 + (seed%2 >= j); seed /= 2;
5580         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5581         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5582         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5583         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5584         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5585         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5586         put(board, exoPieces[0],    0, 0, ANY);
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5588 }
5589
5590 void
5591 InitPosition(redraw)
5592      int redraw;
5593 {
5594     ChessSquare (* pieces)[BOARD_FILES];
5595     int i, j, pawnRow, overrule,
5596     oldx = gameInfo.boardWidth,
5597     oldy = gameInfo.boardHeight,
5598     oldh = gameInfo.holdingsWidth;
5599     static int oldv;
5600
5601     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5602
5603     /* [AS] Initialize pv info list [HGM] and game status */
5604     {
5605         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5606             pvInfoList[i].depth = 0;
5607             boards[i][EP_STATUS] = EP_NONE;
5608             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5609         }
5610
5611         initialRulePlies = 0; /* 50-move counter start */
5612
5613         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5614         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5615     }
5616
5617
5618     /* [HGM] logic here is completely changed. In stead of full positions */
5619     /* the initialized data only consist of the two backranks. The switch */
5620     /* selects which one we will use, which is than copied to the Board   */
5621     /* initialPosition, which for the rest is initialized by Pawns and    */
5622     /* empty squares. This initial position is then copied to boards[0],  */
5623     /* possibly after shuffling, so that it remains available.            */
5624
5625     gameInfo.holdingsWidth = 0; /* default board sizes */
5626     gameInfo.boardWidth    = 8;
5627     gameInfo.boardHeight   = 8;
5628     gameInfo.holdingsSize  = 0;
5629     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5630     for(i=0; i<BOARD_FILES-2; i++)
5631       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5632     initialPosition[EP_STATUS] = EP_NONE;
5633     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5634     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5635          SetCharTable(pieceNickName, appData.pieceNickNames);
5636     else SetCharTable(pieceNickName, "............");
5637     pieces = FIDEArray;
5638
5639     switch (gameInfo.variant) {
5640     case VariantFischeRandom:
5641       shuffleOpenings = TRUE;
5642     default:
5643       break;
5644     case VariantShatranj:
5645       pieces = ShatranjArray;
5646       nrCastlingRights = 0;
5647       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5648       break;
5649     case VariantMakruk:
5650       pieces = makrukArray;
5651       nrCastlingRights = 0;
5652       startedFromSetupPosition = TRUE;
5653       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5654       break;
5655     case VariantTwoKings:
5656       pieces = twoKingsArray;
5657       break;
5658     case VariantCapaRandom:
5659       shuffleOpenings = TRUE;
5660     case VariantCapablanca:
5661       pieces = CapablancaArray;
5662       gameInfo.boardWidth = 10;
5663       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5664       break;
5665     case VariantGothic:
5666       pieces = GothicArray;
5667       gameInfo.boardWidth = 10;
5668       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5669       break;
5670     case VariantSChess:
5671       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5672       gameInfo.holdingsSize = 7;
5673       break;
5674     case VariantJanus:
5675       pieces = JanusArray;
5676       gameInfo.boardWidth = 10;
5677       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5678       nrCastlingRights = 6;
5679         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5680         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5681         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5682         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5683         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5684         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5685       break;
5686     case VariantFalcon:
5687       pieces = FalconArray;
5688       gameInfo.boardWidth = 10;
5689       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5690       break;
5691     case VariantXiangqi:
5692       pieces = XiangqiArray;
5693       gameInfo.boardWidth  = 9;
5694       gameInfo.boardHeight = 10;
5695       nrCastlingRights = 0;
5696       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5697       break;
5698     case VariantShogi:
5699       pieces = ShogiArray;
5700       gameInfo.boardWidth  = 9;
5701       gameInfo.boardHeight = 9;
5702       gameInfo.holdingsSize = 7;
5703       nrCastlingRights = 0;
5704       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5705       break;
5706     case VariantCourier:
5707       pieces = CourierArray;
5708       gameInfo.boardWidth  = 12;
5709       nrCastlingRights = 0;
5710       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5711       break;
5712     case VariantKnightmate:
5713       pieces = KnightmateArray;
5714       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5715       break;
5716     case VariantSpartan:
5717       pieces = SpartanArray;
5718       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5719       break;
5720     case VariantFairy:
5721       pieces = fairyArray;
5722       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5723       break;
5724     case VariantGreat:
5725       pieces = GreatArray;
5726       gameInfo.boardWidth = 10;
5727       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5728       gameInfo.holdingsSize = 8;
5729       break;
5730     case VariantSuper:
5731       pieces = FIDEArray;
5732       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5733       gameInfo.holdingsSize = 8;
5734       startedFromSetupPosition = TRUE;
5735       break;
5736     case VariantCrazyhouse:
5737     case VariantBughouse:
5738       pieces = FIDEArray;
5739       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5740       gameInfo.holdingsSize = 5;
5741       break;
5742     case VariantWildCastle:
5743       pieces = FIDEArray;
5744       /* !!?shuffle with kings guaranteed to be on d or e file */
5745       shuffleOpenings = 1;
5746       break;
5747     case VariantNoCastle:
5748       pieces = FIDEArray;
5749       nrCastlingRights = 0;
5750       /* !!?unconstrained back-rank shuffle */
5751       shuffleOpenings = 1;
5752       break;
5753     }
5754
5755     overrule = 0;
5756     if(appData.NrFiles >= 0) {
5757         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5758         gameInfo.boardWidth = appData.NrFiles;
5759     }
5760     if(appData.NrRanks >= 0) {
5761         gameInfo.boardHeight = appData.NrRanks;
5762     }
5763     if(appData.holdingsSize >= 0) {
5764         i = appData.holdingsSize;
5765         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5766         gameInfo.holdingsSize = i;
5767     }
5768     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5769     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5770         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5771
5772     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5773     if(pawnRow < 1) pawnRow = 1;
5774     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5775
5776     /* User pieceToChar list overrules defaults */
5777     if(appData.pieceToCharTable != NULL)
5778         SetCharTable(pieceToChar, appData.pieceToCharTable);
5779
5780     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5781
5782         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5783             s = (ChessSquare) 0; /* account holding counts in guard band */
5784         for( i=0; i<BOARD_HEIGHT; i++ )
5785             initialPosition[i][j] = s;
5786
5787         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5788         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5789         initialPosition[pawnRow][j] = WhitePawn;
5790         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5791         if(gameInfo.variant == VariantXiangqi) {
5792             if(j&1) {
5793                 initialPosition[pawnRow][j] =
5794                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5795                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5796                    initialPosition[2][j] = WhiteCannon;
5797                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5798                 }
5799             }
5800         }
5801         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5802     }
5803     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5804
5805             j=BOARD_LEFT+1;
5806             initialPosition[1][j] = WhiteBishop;
5807             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5808             j=BOARD_RGHT-2;
5809             initialPosition[1][j] = WhiteRook;
5810             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5811     }
5812
5813     if( nrCastlingRights == -1) {
5814         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5815         /*       This sets default castling rights from none to normal corners   */
5816         /* Variants with other castling rights must set them themselves above    */
5817         nrCastlingRights = 6;
5818
5819         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5822         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5825      }
5826
5827      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5828      if(gameInfo.variant == VariantGreat) { // promotion commoners
5829         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5830         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5831         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5832         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5833      }
5834      if( gameInfo.variant == VariantSChess ) {
5835       initialPosition[1][0] = BlackMarshall;
5836       initialPosition[2][0] = BlackAngel;
5837       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5838       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5839       initialPosition[1][1] = initialPosition[2][1] = 
5840       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5841      }
5842   if (appData.debugMode) {
5843     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5844   }
5845     if(shuffleOpenings) {
5846         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5847         startedFromSetupPosition = TRUE;
5848     }
5849     if(startedFromPositionFile) {
5850       /* [HGM] loadPos: use PositionFile for every new game */
5851       CopyBoard(initialPosition, filePosition);
5852       for(i=0; i<nrCastlingRights; i++)
5853           initialRights[i] = filePosition[CASTLING][i];
5854       startedFromSetupPosition = TRUE;
5855     }
5856
5857     CopyBoard(boards[0], initialPosition);
5858
5859     if(oldx != gameInfo.boardWidth ||
5860        oldy != gameInfo.boardHeight ||
5861        oldv != gameInfo.variant ||
5862        oldh != gameInfo.holdingsWidth
5863                                          )
5864             InitDrawingSizes(-2 ,0);
5865
5866     oldv = gameInfo.variant;
5867     if (redraw)
5868       DrawPosition(TRUE, boards[currentMove]);
5869 }
5870
5871 void
5872 SendBoard(cps, moveNum)
5873      ChessProgramState *cps;
5874      int moveNum;
5875 {
5876     char message[MSG_SIZ];
5877
5878     if (cps->useSetboard) {
5879       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5880       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5881       SendToProgram(message, cps);
5882       free(fen);
5883
5884     } else {
5885       ChessSquare *bp;
5886       int i, j;
5887       /* Kludge to set black to move, avoiding the troublesome and now
5888        * deprecated "black" command.
5889        */
5890       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5891         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5892
5893       SendToProgram("edit\n", cps);
5894       SendToProgram("#\n", cps);
5895       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5896         bp = &boards[moveNum][i][BOARD_LEFT];
5897         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5898           if ((int) *bp < (int) BlackPawn) {
5899             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5900                     AAA + j, ONE + i);
5901             if(message[0] == '+' || message[0] == '~') {
5902               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5903                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5904                         AAA + j, ONE + i);
5905             }
5906             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5907                 message[1] = BOARD_RGHT   - 1 - j + '1';
5908                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5909             }
5910             SendToProgram(message, cps);
5911           }
5912         }
5913       }
5914
5915       SendToProgram("c\n", cps);
5916       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5917         bp = &boards[moveNum][i][BOARD_LEFT];
5918         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5919           if (((int) *bp != (int) EmptySquare)
5920               && ((int) *bp >= (int) BlackPawn)) {
5921             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5922                     AAA + j, ONE + i);
5923             if(message[0] == '+' || message[0] == '~') {
5924               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5925                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5926                         AAA + j, ONE + i);
5927             }
5928             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5929                 message[1] = BOARD_RGHT   - 1 - j + '1';
5930                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5931             }
5932             SendToProgram(message, cps);
5933           }
5934         }
5935       }
5936
5937       SendToProgram(".\n", cps);
5938     }
5939     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5940 }
5941
5942 ChessSquare
5943 DefaultPromoChoice(int white)
5944 {
5945     ChessSquare result;
5946     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5947         result = WhiteFerz; // no choice
5948     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5949         result= WhiteKing; // in Suicide Q is the last thing we want
5950     else if(gameInfo.variant == VariantSpartan)
5951         result = white ? WhiteQueen : WhiteAngel;
5952     else result = WhiteQueen;
5953     if(!white) result = WHITE_TO_BLACK result;
5954     return result;
5955 }
5956
5957 static int autoQueen; // [HGM] oneclick
5958
5959 int
5960 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5961 {
5962     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5963     /* [HGM] add Shogi promotions */
5964     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5965     ChessSquare piece;
5966     ChessMove moveType;
5967     Boolean premove;
5968
5969     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5970     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5971
5972     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5973       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5974         return FALSE;
5975
5976     piece = boards[currentMove][fromY][fromX];
5977     if(gameInfo.variant == VariantShogi) {
5978         promotionZoneSize = BOARD_HEIGHT/3;
5979         highestPromotingPiece = (int)WhiteFerz;
5980     } else if(gameInfo.variant == VariantMakruk) {
5981         promotionZoneSize = 3;
5982     }
5983
5984     // Treat Lance as Pawn when it is not representing Amazon
5985     if(gameInfo.variant != VariantSuper) {
5986         if(piece == WhiteLance) piece = WhitePawn; else
5987         if(piece == BlackLance) piece = BlackPawn;
5988     }
5989
5990     // next weed out all moves that do not touch the promotion zone at all
5991     if((int)piece >= BlackPawn) {
5992         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5993              return FALSE;
5994         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5995     } else {
5996         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5997            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5998     }
5999
6000     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6001
6002     // weed out mandatory Shogi promotions
6003     if(gameInfo.variant == VariantShogi) {
6004         if(piece >= BlackPawn) {
6005             if(toY == 0 && piece == BlackPawn ||
6006                toY == 0 && piece == BlackQueen ||
6007                toY <= 1 && piece == BlackKnight) {
6008                 *promoChoice = '+';
6009                 return FALSE;
6010             }
6011         } else {
6012             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6013                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6014                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6015                 *promoChoice = '+';
6016                 return FALSE;
6017             }
6018         }
6019     }
6020
6021     // weed out obviously illegal Pawn moves
6022     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6023         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6024         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6025         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6026         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6027         // note we are not allowed to test for valid (non-)capture, due to premove
6028     }
6029
6030     // we either have a choice what to promote to, or (in Shogi) whether to promote
6031     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6032         *promoChoice = PieceToChar(BlackFerz);  // no choice
6033         return FALSE;
6034     }
6035     // no sense asking what we must promote to if it is going to explode...
6036     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6037         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6038         return FALSE;
6039     }
6040     // give caller the default choice even if we will not make it
6041     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6042     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6043     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6044                            && gameInfo.variant != VariantShogi
6045                            && gameInfo.variant != VariantSuper) return FALSE;
6046     if(autoQueen) return FALSE; // predetermined
6047
6048     // suppress promotion popup on illegal moves that are not premoves
6049     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6050               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6051     if(appData.testLegality && !premove) {
6052         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6053                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6054         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6055             return FALSE;
6056     }
6057
6058     return TRUE;
6059 }
6060
6061 int
6062 InPalace(row, column)
6063      int row, column;
6064 {   /* [HGM] for Xiangqi */
6065     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6066          column < (BOARD_WIDTH + 4)/2 &&
6067          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6068     return FALSE;
6069 }
6070
6071 int
6072 PieceForSquare (x, y)
6073      int x;
6074      int y;
6075 {
6076   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6077      return -1;
6078   else
6079      return boards[currentMove][y][x];
6080 }
6081
6082 int
6083 OKToStartUserMove(x, y)
6084      int x, y;
6085 {
6086     ChessSquare from_piece;
6087     int white_piece;
6088
6089     if (matchMode) return FALSE;
6090     if (gameMode == EditPosition) return TRUE;
6091
6092     if (x >= 0 && y >= 0)
6093       from_piece = boards[currentMove][y][x];
6094     else
6095       from_piece = EmptySquare;
6096
6097     if (from_piece == EmptySquare) return FALSE;
6098
6099     white_piece = (int)from_piece >= (int)WhitePawn &&
6100       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6101
6102     switch (gameMode) {
6103       case PlayFromGameFile:
6104       case AnalyzeFile:
6105       case TwoMachinesPlay:
6106       case EndOfGame:
6107         return FALSE;
6108
6109       case IcsObserving:
6110       case IcsIdle:
6111         return FALSE;
6112
6113       case MachinePlaysWhite:
6114       case IcsPlayingBlack:
6115         if (appData.zippyPlay) return FALSE;
6116         if (white_piece) {
6117             DisplayMoveError(_("You are playing Black"));
6118             return FALSE;
6119         }
6120         break;
6121
6122       case MachinePlaysBlack:
6123       case IcsPlayingWhite:
6124         if (appData.zippyPlay) return FALSE;
6125         if (!white_piece) {
6126             DisplayMoveError(_("You are playing White"));
6127             return FALSE;
6128         }
6129         break;
6130
6131       case EditGame:
6132         if (!white_piece && WhiteOnMove(currentMove)) {
6133             DisplayMoveError(_("It is White's turn"));
6134             return FALSE;
6135         }
6136         if (white_piece && !WhiteOnMove(currentMove)) {
6137             DisplayMoveError(_("It is Black's turn"));
6138             return FALSE;
6139         }
6140         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6141             /* Editing correspondence game history */
6142             /* Could disallow this or prompt for confirmation */
6143             cmailOldMove = -1;
6144         }
6145         break;
6146
6147       case BeginningOfGame:
6148         if (appData.icsActive) return FALSE;
6149         if (!appData.noChessProgram) {
6150             if (!white_piece) {
6151                 DisplayMoveError(_("You are playing White"));
6152                 return FALSE;
6153             }
6154         }
6155         break;
6156
6157       case Training:
6158         if (!white_piece && WhiteOnMove(currentMove)) {
6159             DisplayMoveError(_("It is White's turn"));
6160             return FALSE;
6161         }
6162         if (white_piece && !WhiteOnMove(currentMove)) {
6163             DisplayMoveError(_("It is Black's turn"));
6164             return FALSE;
6165         }
6166         break;
6167
6168       default:
6169       case IcsExamining:
6170         break;
6171     }
6172     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6173         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6174         && gameMode != AnalyzeFile && gameMode != Training) {
6175         DisplayMoveError(_("Displayed position is not current"));
6176         return FALSE;
6177     }
6178     return TRUE;
6179 }
6180
6181 Boolean
6182 OnlyMove(int *x, int *y, Boolean captures) {
6183     DisambiguateClosure cl;
6184     if (appData.zippyPlay) return FALSE;
6185     switch(gameMode) {
6186       case MachinePlaysBlack:
6187       case IcsPlayingWhite:
6188       case BeginningOfGame:
6189         if(!WhiteOnMove(currentMove)) return FALSE;
6190         break;
6191       case MachinePlaysWhite:
6192       case IcsPlayingBlack:
6193         if(WhiteOnMove(currentMove)) return FALSE;
6194         break;
6195       case EditGame:
6196         break;
6197       default:
6198         return FALSE;
6199     }
6200     cl.pieceIn = EmptySquare;
6201     cl.rfIn = *y;
6202     cl.ffIn = *x;
6203     cl.rtIn = -1;
6204     cl.ftIn = -1;
6205     cl.promoCharIn = NULLCHAR;
6206     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6207     if( cl.kind == NormalMove ||
6208         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6209         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6210         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6211       fromX = cl.ff;
6212       fromY = cl.rf;
6213       *x = cl.ft;
6214       *y = cl.rt;
6215       return TRUE;
6216     }
6217     if(cl.kind != ImpossibleMove) return FALSE;
6218     cl.pieceIn = EmptySquare;
6219     cl.rfIn = -1;
6220     cl.ffIn = -1;
6221     cl.rtIn = *y;
6222     cl.ftIn = *x;
6223     cl.promoCharIn = NULLCHAR;
6224     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6225     if( cl.kind == NormalMove ||
6226         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6227         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6228         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6229       fromX = cl.ff;
6230       fromY = cl.rf;
6231       *x = cl.ft;
6232       *y = cl.rt;
6233       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6234       return TRUE;
6235     }
6236     return FALSE;
6237 }
6238
6239 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6240 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6241 int lastLoadGameUseList = FALSE;
6242 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6243 ChessMove lastLoadGameStart = EndOfFile;
6244
6245 void
6246 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6247      int fromX, fromY, toX, toY;
6248      int promoChar;
6249 {
6250     ChessMove moveType;
6251     ChessSquare pdown, pup;
6252
6253     /* Check if the user is playing in turn.  This is complicated because we
6254        let the user "pick up" a piece before it is his turn.  So the piece he
6255        tried to pick up may have been captured by the time he puts it down!
6256        Therefore we use the color the user is supposed to be playing in this
6257        test, not the color of the piece that is currently on the starting
6258        square---except in EditGame mode, where the user is playing both
6259        sides; fortunately there the capture race can't happen.  (It can
6260        now happen in IcsExamining mode, but that's just too bad.  The user
6261        will get a somewhat confusing message in that case.)
6262        */
6263
6264     switch (gameMode) {
6265       case PlayFromGameFile:
6266       case AnalyzeFile:
6267       case TwoMachinesPlay:
6268       case EndOfGame:
6269       case IcsObserving:
6270       case IcsIdle:
6271         /* We switched into a game mode where moves are not accepted,
6272            perhaps while the mouse button was down. */
6273         return;
6274
6275       case MachinePlaysWhite:
6276         /* User is moving for Black */
6277         if (WhiteOnMove(currentMove)) {
6278             DisplayMoveError(_("It is White's turn"));
6279             return;
6280         }
6281         break;
6282
6283       case MachinePlaysBlack:
6284         /* User is moving for White */
6285         if (!WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is Black's turn"));
6287             return;
6288         }
6289         break;
6290
6291       case EditGame:
6292       case IcsExamining:
6293       case BeginningOfGame:
6294       case AnalyzeMode:
6295       case Training:
6296         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6297         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6298             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6299             /* User is moving for Black */
6300             if (WhiteOnMove(currentMove)) {
6301                 DisplayMoveError(_("It is White's turn"));
6302                 return;
6303             }
6304         } else {
6305             /* User is moving for White */
6306             if (!WhiteOnMove(currentMove)) {
6307                 DisplayMoveError(_("It is Black's turn"));
6308                 return;
6309             }
6310         }
6311         break;
6312
6313       case IcsPlayingBlack:
6314         /* User is moving for Black */
6315         if (WhiteOnMove(currentMove)) {
6316             if (!appData.premove) {
6317                 DisplayMoveError(_("It is White's turn"));
6318             } else if (toX >= 0 && toY >= 0) {
6319                 premoveToX = toX;
6320                 premoveToY = toY;
6321                 premoveFromX = fromX;
6322                 premoveFromY = fromY;
6323                 premovePromoChar = promoChar;
6324                 gotPremove = 1;
6325                 if (appData.debugMode)
6326                     fprintf(debugFP, "Got premove: fromX %d,"
6327                             "fromY %d, toX %d, toY %d\n",
6328                             fromX, fromY, toX, toY);
6329             }
6330             return;
6331         }
6332         break;
6333
6334       case IcsPlayingWhite:
6335         /* User is moving for White */
6336         if (!WhiteOnMove(currentMove)) {
6337             if (!appData.premove) {
6338                 DisplayMoveError(_("It is Black's turn"));
6339             } else if (toX >= 0 && toY >= 0) {
6340                 premoveToX = toX;
6341                 premoveToY = toY;
6342                 premoveFromX = fromX;
6343                 premoveFromY = fromY;
6344                 premovePromoChar = promoChar;
6345                 gotPremove = 1;
6346                 if (appData.debugMode)
6347                     fprintf(debugFP, "Got premove: fromX %d,"
6348                             "fromY %d, toX %d, toY %d\n",
6349                             fromX, fromY, toX, toY);
6350             }
6351             return;
6352         }
6353         break;
6354
6355       default:
6356         break;
6357
6358       case EditPosition:
6359         /* EditPosition, empty square, or different color piece;
6360            click-click move is possible */
6361         if (toX == -2 || toY == -2) {
6362             boards[0][fromY][fromX] = EmptySquare;
6363             DrawPosition(FALSE, boards[currentMove]);
6364             return;
6365         } else if (toX >= 0 && toY >= 0) {
6366             boards[0][toY][toX] = boards[0][fromY][fromX];
6367             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6368                 if(boards[0][fromY][0] != EmptySquare) {
6369                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6370                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6371                 }
6372             } else
6373             if(fromX == BOARD_RGHT+1) {
6374                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6375                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6376                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6377                 }
6378             } else
6379             boards[0][fromY][fromX] = EmptySquare;
6380             DrawPosition(FALSE, boards[currentMove]);
6381             return;
6382         }
6383         return;
6384     }
6385
6386     if(toX < 0 || toY < 0) return;
6387     pdown = boards[currentMove][fromY][fromX];
6388     pup = boards[currentMove][toY][toX];
6389
6390     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6391     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6392          if( pup != EmptySquare ) return;
6393          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6394            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6395                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6396            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6397            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6398            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6399            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6400          fromY = DROP_RANK;
6401     }
6402
6403     /* [HGM] always test for legality, to get promotion info */
6404     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6405                                          fromY, fromX, toY, toX, promoChar);
6406     /* [HGM] but possibly ignore an IllegalMove result */
6407     if (appData.testLegality) {
6408         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6409             DisplayMoveError(_("Illegal move"));
6410             return;
6411         }
6412     }
6413
6414     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6415 }
6416
6417 /* Common tail of UserMoveEvent and DropMenuEvent */
6418 int
6419 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6420      ChessMove moveType;
6421      int fromX, fromY, toX, toY;
6422      /*char*/int promoChar;
6423 {
6424     char *bookHit = 0;
6425
6426     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6427         // [HGM] superchess: suppress promotions to non-available piece
6428         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6429         if(WhiteOnMove(currentMove)) {
6430             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6431         } else {
6432             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6433         }
6434     }
6435
6436     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6437        move type in caller when we know the move is a legal promotion */
6438     if(moveType == NormalMove && promoChar)
6439         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6440
6441     /* [HGM] <popupFix> The following if has been moved here from
6442        UserMoveEvent(). Because it seemed to belong here (why not allow
6443        piece drops in training games?), and because it can only be
6444        performed after it is known to what we promote. */
6445     if (gameMode == Training) {
6446       /* compare the move played on the board to the next move in the
6447        * game. If they match, display the move and the opponent's response.
6448        * If they don't match, display an error message.
6449        */
6450       int saveAnimate;
6451       Board testBoard;
6452       CopyBoard(testBoard, boards[currentMove]);
6453       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6454
6455       if (CompareBoards(testBoard, boards[currentMove+1])) {
6456         ForwardInner(currentMove+1);
6457
6458         /* Autoplay the opponent's response.
6459          * if appData.animate was TRUE when Training mode was entered,
6460          * the response will be animated.
6461          */
6462         saveAnimate = appData.animate;
6463         appData.animate = animateTraining;
6464         ForwardInner(currentMove+1);
6465         appData.animate = saveAnimate;
6466
6467         /* check for the end of the game */
6468         if (currentMove >= forwardMostMove) {
6469           gameMode = PlayFromGameFile;
6470           ModeHighlight();
6471           SetTrainingModeOff();
6472           DisplayInformation(_("End of game"));
6473         }
6474       } else {
6475         DisplayError(_("Incorrect move"), 0);
6476       }
6477       return 1;
6478     }
6479
6480   /* Ok, now we know that the move is good, so we can kill
6481      the previous line in Analysis Mode */
6482   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6483                                 && currentMove < forwardMostMove) {
6484     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6485     else forwardMostMove = currentMove;
6486   }
6487
6488   /* If we need the chess program but it's dead, restart it */
6489   ResurrectChessProgram();
6490
6491   /* A user move restarts a paused game*/
6492   if (pausing)
6493     PauseEvent();
6494
6495   thinkOutput[0] = NULLCHAR;
6496
6497   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6498
6499   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6500     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6501     return 1;
6502   }
6503
6504   if (gameMode == BeginningOfGame) {
6505     if (appData.noChessProgram) {
6506       gameMode = EditGame;
6507       SetGameInfo();
6508     } else {
6509       char buf[MSG_SIZ];
6510       gameMode = MachinePlaysBlack;
6511       StartClocks();
6512       SetGameInfo();
6513       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6514       DisplayTitle(buf);
6515       if (first.sendName) {
6516         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6517         SendToProgram(buf, &first);
6518       }
6519       StartClocks();
6520     }
6521     ModeHighlight();
6522   }
6523
6524   /* Relay move to ICS or chess engine */
6525   if (appData.icsActive) {
6526     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6527         gameMode == IcsExamining) {
6528       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6529         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6530         SendToICS("draw ");
6531         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6532       }
6533       // also send plain move, in case ICS does not understand atomic claims
6534       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6535       ics_user_moved = 1;
6536     }
6537   } else {
6538     if (first.sendTime && (gameMode == BeginningOfGame ||
6539                            gameMode == MachinePlaysWhite ||
6540                            gameMode == MachinePlaysBlack)) {
6541       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6542     }
6543     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6544          // [HGM] book: if program might be playing, let it use book
6545         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6546         first.maybeThinking = TRUE;
6547     } else SendMoveToProgram(forwardMostMove-1, &first);
6548     if (currentMove == cmailOldMove + 1) {
6549       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6550     }
6551   }
6552
6553   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6554
6555   switch (gameMode) {
6556   case EditGame:
6557     if(appData.testLegality)
6558     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6559     case MT_NONE:
6560     case MT_CHECK:
6561       break;
6562     case MT_CHECKMATE:
6563     case MT_STAINMATE:
6564       if (WhiteOnMove(currentMove)) {
6565         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6566       } else {
6567         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6568       }
6569       break;
6570     case MT_STALEMATE:
6571       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6572       break;
6573     }
6574     break;
6575
6576   case MachinePlaysBlack:
6577   case MachinePlaysWhite:
6578     /* disable certain menu options while machine is thinking */
6579     SetMachineThinkingEnables();
6580     break;
6581
6582   default:
6583     break;
6584   }
6585
6586   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6587   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6588
6589   if(bookHit) { // [HGM] book: simulate book reply
6590         static char bookMove[MSG_SIZ]; // a bit generous?
6591
6592         programStats.nodes = programStats.depth = programStats.time =
6593         programStats.score = programStats.got_only_move = 0;
6594         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6595
6596         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6597         strcat(bookMove, bookHit);
6598         HandleMachineMove(bookMove, &first);
6599   }
6600   return 1;
6601 }
6602
6603 void
6604 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6605      Board board;
6606      int flags;
6607      ChessMove kind;
6608      int rf, ff, rt, ft;
6609      VOIDSTAR closure;
6610 {
6611     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6612     Markers *m = (Markers *) closure;
6613     if(rf == fromY && ff == fromX)
6614         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6615                          || kind == WhiteCapturesEnPassant
6616                          || kind == BlackCapturesEnPassant);
6617     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6618 }
6619
6620 void
6621 MarkTargetSquares(int clear)
6622 {
6623   int x, y;
6624   if(!appData.markers || !appData.highlightDragging ||
6625      !appData.testLegality || gameMode == EditPosition) return;
6626   if(clear) {
6627     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6628   } else {
6629     int capt = 0;
6630     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6631     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6632       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6633       if(capt)
6634       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6635     }
6636   }
6637   DrawPosition(TRUE, NULL);
6638 }
6639
6640 int
6641 Explode(Board board, int fromX, int fromY, int toX, int toY)
6642 {
6643     if(gameInfo.variant == VariantAtomic &&
6644        (board[toY][toX] != EmptySquare ||                     // capture?
6645         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6646                          board[fromY][fromX] == BlackPawn   )
6647       )) {
6648         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6649         return TRUE;
6650     }
6651     return FALSE;
6652 }
6653
6654 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6655
6656 int CanPromote(ChessSquare piece, int y)
6657 {
6658         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6659         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6660         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6661            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6662            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6663                                                   gameInfo.variant == VariantMakruk) return FALSE;
6664         return (piece == BlackPawn && y == 1 ||
6665                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6666                 piece == BlackLance && y == 1 ||
6667                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6668 }
6669
6670 void LeftClick(ClickType clickType, int xPix, int yPix)
6671 {
6672     int x, y;
6673     Boolean saveAnimate;
6674     static int second = 0, promotionChoice = 0, clearFlag = 0;
6675     char promoChoice = NULLCHAR;
6676     ChessSquare piece;
6677
6678     if(appData.seekGraph && appData.icsActive && loggedOn &&
6679         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6680         SeekGraphClick(clickType, xPix, yPix, 0);
6681         return;
6682     }
6683
6684     if (clickType == Press) ErrorPopDown();
6685     MarkTargetSquares(1);
6686
6687     x = EventToSquare(xPix, BOARD_WIDTH);
6688     y = EventToSquare(yPix, BOARD_HEIGHT);
6689     if (!flipView && y >= 0) {
6690         y = BOARD_HEIGHT - 1 - y;
6691     }
6692     if (flipView && x >= 0) {
6693         x = BOARD_WIDTH - 1 - x;
6694     }
6695
6696     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6697         defaultPromoChoice = promoSweep;
6698         promoSweep = EmptySquare;   // terminate sweep
6699         promoDefaultAltered = TRUE;
6700         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6701     }
6702
6703     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6704         if(clickType == Release) return; // ignore upclick of click-click destination
6705         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6706         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6707         if(gameInfo.holdingsWidth &&
6708                 (WhiteOnMove(currentMove)
6709                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6710                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6711             // click in right holdings, for determining promotion piece
6712             ChessSquare p = boards[currentMove][y][x];
6713             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6714             if(p != EmptySquare) {
6715                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6716                 fromX = fromY = -1;
6717                 return;
6718             }
6719         }
6720         DrawPosition(FALSE, boards[currentMove]);
6721         return;
6722     }
6723
6724     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6725     if(clickType == Press
6726             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6727               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6728               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6729         return;
6730
6731     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6732         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6733
6734     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6735         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6736                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6737         defaultPromoChoice = DefaultPromoChoice(side);
6738     }
6739
6740     autoQueen = appData.alwaysPromoteToQueen;
6741
6742     if (fromX == -1) {
6743       int originalY = y;
6744       gatingPiece = EmptySquare;
6745       if (clickType != Press) {
6746         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6747             DragPieceEnd(xPix, yPix); dragging = 0;
6748             DrawPosition(FALSE, NULL);
6749         }
6750         return;
6751       }
6752       fromX = x; fromY = y;
6753       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6754          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6755          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6756             /* First square */
6757             if (OKToStartUserMove(fromX, fromY)) {
6758                 second = 0;
6759                 MarkTargetSquares(0);
6760                 DragPieceBegin(xPix, yPix); dragging = 1;
6761                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6762                     promoSweep = defaultPromoChoice;
6763                     selectFlag = 0; lastX = xPix; lastY = yPix;
6764                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6765                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6766                 }
6767                 if (appData.highlightDragging) {
6768                     SetHighlights(fromX, fromY, -1, -1);
6769                 }
6770             } else fromX = fromY = -1;
6771             return;
6772         }
6773     }
6774
6775     /* fromX != -1 */
6776     if (clickType == Press && gameMode != EditPosition) {
6777         ChessSquare fromP;
6778         ChessSquare toP;
6779         int frc;
6780
6781         // ignore off-board to clicks
6782         if(y < 0 || x < 0) return;
6783
6784         /* Check if clicking again on the same color piece */
6785         fromP = boards[currentMove][fromY][fromX];
6786         toP = boards[currentMove][y][x];
6787         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6788         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6789              WhitePawn <= toP && toP <= WhiteKing &&
6790              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6791              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6792             (BlackPawn <= fromP && fromP <= BlackKing &&
6793              BlackPawn <= toP && toP <= BlackKing &&
6794              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6795              !(fromP == BlackKing && toP == BlackRook && frc))) {
6796             /* Clicked again on same color piece -- changed his mind */
6797             second = (x == fromX && y == fromY);
6798             promoDefaultAltered = FALSE;
6799            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6800             if (appData.highlightDragging) {
6801                 SetHighlights(x, y, -1, -1);
6802             } else {
6803                 ClearHighlights();
6804             }
6805             if (OKToStartUserMove(x, y)) {
6806                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6807                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6808                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6809                  gatingPiece = boards[currentMove][fromY][fromX];
6810                 else gatingPiece = EmptySquare;
6811                 fromX = x;
6812                 fromY = y; dragging = 1;
6813                 MarkTargetSquares(0);
6814                 DragPieceBegin(xPix, yPix);
6815                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6816                     promoSweep = defaultPromoChoice;
6817                     selectFlag = 0; lastX = xPix; lastY = yPix;
6818                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6819                 }
6820             }
6821            }
6822            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6823            second = FALSE; 
6824         }
6825         // ignore clicks on holdings
6826         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6827     }
6828
6829     if (clickType == Release && x == fromX && y == fromY) {
6830         DragPieceEnd(xPix, yPix); dragging = 0;
6831         if(clearFlag) {
6832             // a deferred attempt to click-click move an empty square on top of a piece
6833             boards[currentMove][y][x] = EmptySquare;
6834             ClearHighlights();
6835             DrawPosition(FALSE, boards[currentMove]);
6836             fromX = fromY = -1; clearFlag = 0;
6837             return;
6838         }
6839         if (appData.animateDragging) {
6840             /* Undo animation damage if any */
6841             DrawPosition(FALSE, NULL);
6842         }
6843         if (second) {
6844             /* Second up/down in same square; just abort move */
6845             second = 0;
6846             fromX = fromY = -1;
6847             gatingPiece = EmptySquare;
6848             ClearHighlights();
6849             gotPremove = 0;
6850             ClearPremoveHighlights();
6851         } else {
6852             /* First upclick in same square; start click-click mode */
6853             SetHighlights(x, y, -1, -1);
6854         }
6855         return;
6856     }
6857
6858     clearFlag = 0;
6859
6860     /* we now have a different from- and (possibly off-board) to-square */
6861     /* Completed move */
6862     toX = x;
6863     toY = y;
6864     saveAnimate = appData.animate;
6865     if (clickType == Press) {
6866         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6867             // must be Edit Position mode with empty-square selected
6868             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6869             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6870             return;
6871         }
6872         /* Finish clickclick move */
6873         if (appData.animate || appData.highlightLastMove) {
6874             SetHighlights(fromX, fromY, toX, toY);
6875         } else {
6876             ClearHighlights();
6877         }
6878     } else {
6879         /* Finish drag move */
6880         if (appData.highlightLastMove) {
6881             SetHighlights(fromX, fromY, toX, toY);
6882         } else {
6883             ClearHighlights();
6884         }
6885         DragPieceEnd(xPix, yPix); dragging = 0;
6886         /* Don't animate move and drag both */
6887         appData.animate = FALSE;
6888     }
6889
6890     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6891     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6892         ChessSquare piece = boards[currentMove][fromY][fromX];
6893         if(gameMode == EditPosition && piece != EmptySquare &&
6894            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6895             int n;
6896
6897             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6898                 n = PieceToNumber(piece - (int)BlackPawn);
6899                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6900                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6901                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6902             } else
6903             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6904                 n = PieceToNumber(piece);
6905                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6906                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6907                 boards[currentMove][n][BOARD_WIDTH-2]++;
6908             }
6909             boards[currentMove][fromY][fromX] = EmptySquare;
6910         }
6911         ClearHighlights();
6912         fromX = fromY = -1;
6913         DrawPosition(TRUE, boards[currentMove]);
6914         return;
6915     }
6916
6917     // off-board moves should not be highlighted
6918     if(x < 0 || y < 0) ClearHighlights();
6919
6920     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6921
6922     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6923         SetHighlights(fromX, fromY, toX, toY);
6924         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6925             // [HGM] super: promotion to captured piece selected from holdings
6926             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6927             promotionChoice = TRUE;
6928             // kludge follows to temporarily execute move on display, without promoting yet
6929             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6930             boards[currentMove][toY][toX] = p;
6931             DrawPosition(FALSE, boards[currentMove]);
6932             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6933             boards[currentMove][toY][toX] = q;
6934             DisplayMessage("Click in holdings to choose piece", "");
6935             return;
6936         }
6937         PromotionPopUp();
6938     } else {
6939         int oldMove = currentMove;
6940         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6941         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6942         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6943         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6944            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6945             DrawPosition(TRUE, boards[currentMove]);
6946         fromX = fromY = -1;
6947     }
6948     appData.animate = saveAnimate;
6949     if (appData.animate || appData.animateDragging) {
6950         /* Undo animation damage if needed */
6951         DrawPosition(FALSE, NULL);
6952     }
6953 }
6954
6955 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6956 {   // front-end-free part taken out of PieceMenuPopup
6957     int whichMenu; int xSqr, ySqr;
6958
6959     if(seekGraphUp) { // [HGM] seekgraph
6960         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6961         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6962         return -2;
6963     }
6964
6965     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6966          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6967         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6968         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6969         if(action == Press)   {
6970             originalFlip = flipView;
6971             flipView = !flipView; // temporarily flip board to see game from partners perspective
6972             DrawPosition(TRUE, partnerBoard);
6973             DisplayMessage(partnerStatus, "");
6974             partnerUp = TRUE;
6975         } else if(action == Release) {
6976             flipView = originalFlip;
6977             DrawPosition(TRUE, boards[currentMove]);
6978             partnerUp = FALSE;
6979         }
6980         return -2;
6981     }
6982
6983     xSqr = EventToSquare(x, BOARD_WIDTH);
6984     ySqr = EventToSquare(y, BOARD_HEIGHT);
6985     if (action == Release) {
6986         if(pieceSweep != EmptySquare) {
6987             EditPositionMenuEvent(pieceSweep, toX, toY);
6988             pieceSweep = EmptySquare;
6989         } else UnLoadPV(); // [HGM] pv
6990     }
6991     if (action != Press) return -2; // return code to be ignored
6992     switch (gameMode) {
6993       case IcsExamining:
6994         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6995       case EditPosition:
6996         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6997         if (xSqr < 0 || ySqr < 0) return -1;
6998         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6999         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7000         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7001         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7002         NextPiece(0);
7003         return -2;\r
7004       case IcsObserving:
7005         if(!appData.icsEngineAnalyze) return -1;
7006       case IcsPlayingWhite:
7007       case IcsPlayingBlack:
7008         if(!appData.zippyPlay) goto noZip;
7009       case AnalyzeMode:
7010       case AnalyzeFile:
7011       case MachinePlaysWhite:
7012       case MachinePlaysBlack:
7013       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7014         if (!appData.dropMenu) {
7015           LoadPV(x, y);
7016           return 2; // flag front-end to grab mouse events
7017         }
7018         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7019            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7020       case EditGame:
7021       noZip:
7022         if (xSqr < 0 || ySqr < 0) return -1;
7023         if (!appData.dropMenu || appData.testLegality &&
7024             gameInfo.variant != VariantBughouse &&
7025             gameInfo.variant != VariantCrazyhouse) return -1;
7026         whichMenu = 1; // drop menu
7027         break;
7028       default:
7029         return -1;
7030     }
7031
7032     if (((*fromX = xSqr) < 0) ||
7033         ((*fromY = ySqr) < 0)) {
7034         *fromX = *fromY = -1;
7035         return -1;
7036     }
7037     if (flipView)
7038       *fromX = BOARD_WIDTH - 1 - *fromX;
7039     else
7040       *fromY = BOARD_HEIGHT - 1 - *fromY;
7041
7042     return whichMenu;
7043 }
7044
7045 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7046 {
7047 //    char * hint = lastHint;
7048     FrontEndProgramStats stats;
7049
7050     stats.which = cps == &first ? 0 : 1;
7051     stats.depth = cpstats->depth;
7052     stats.nodes = cpstats->nodes;
7053     stats.score = cpstats->score;
7054     stats.time = cpstats->time;
7055     stats.pv = cpstats->movelist;
7056     stats.hint = lastHint;
7057     stats.an_move_index = 0;
7058     stats.an_move_count = 0;
7059
7060     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7061         stats.hint = cpstats->move_name;
7062         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7063         stats.an_move_count = cpstats->nr_moves;
7064     }
7065
7066     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
7067
7068     SetProgramStats( &stats );
7069 }
7070
7071 #define MAXPLAYERS 500
7072
7073 char *
7074 TourneyStandings(int display)
7075 {
7076     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7077     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7078     char result, *p, *names[MAXPLAYERS];
7079
7080     names[0] = p = strdup(appData.participants);
7081     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7082
7083     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7084
7085     while(result = appData.results[nr]) {
7086         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7087         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7088         wScore = bScore = 0;
7089         switch(result) {
7090           case '+': wScore = 2; break;
7091           case '-': bScore = 2; break;
7092           case '=': wScore = bScore = 1; break;
7093           case ' ':
7094           case '*': return strdup("busy"); // tourney not finished
7095         }
7096         score[w] += wScore;
7097         score[b] += bScore;
7098         games[w]++;
7099         games[b]++;
7100         nr++;
7101     }
7102     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7103     for(w=0; w<nPlayers; w++) {
7104         bScore = -1;
7105         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7106         ranking[w] = b; points[w] = bScore; score[b] = -2;
7107     }
7108     p = malloc(nPlayers*34+1);
7109     for(w=0; w<nPlayers && w<display; w++)
7110         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7111     free(names[0]);
7112     return p;
7113 }
7114
7115 void
7116 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7117 {       // count all piece types
7118         int p, f, r;
7119         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7120         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7121         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7122                 p = board[r][f];
7123                 pCnt[p]++;
7124                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7125                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7126                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7127                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7128                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7129                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7130         }
7131 }
7132
7133 int
7134 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7135 {
7136         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7137         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7138
7139         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7140         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7141         if(myPawns == 2 && nMine == 3) // KPP
7142             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7143         if(myPawns == 1 && nMine == 2) // KP
7144             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7145         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7146             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7147         if(myPawns) return FALSE;
7148         if(pCnt[WhiteRook+side])
7149             return pCnt[BlackRook-side] ||
7150                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7151                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7152                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7153         if(pCnt[WhiteCannon+side]) {
7154             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7155             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7156         }
7157         if(pCnt[WhiteKnight+side])
7158             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7159         return FALSE;
7160 }
7161
7162 int
7163 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7164 {
7165         VariantClass v = gameInfo.variant;
7166
7167         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7168         if(v == VariantShatranj) return TRUE; // always winnable through baring
7169         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7170         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7171
7172         if(v == VariantXiangqi) {
7173                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7174
7175                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7176                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7177                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7178                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7179                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7180                 if(stale) // we have at least one last-rank P plus perhaps C
7181                     return majors // KPKX
7182                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7183                 else // KCA*E*
7184                     return pCnt[WhiteFerz+side] // KCAK
7185                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7186                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7187                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7188
7189         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7190                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7191
7192                 if(nMine == 1) return FALSE; // bare King
7193                 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
7194                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7195                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7196                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7197                 if(pCnt[WhiteKnight+side])
7198                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7199                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7200                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7201                 if(nBishops)
7202                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7203                 if(pCnt[WhiteAlfil+side])
7204                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7205                 if(pCnt[WhiteWazir+side])
7206                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7207         }
7208
7209         return TRUE;
7210 }
7211
7212 int
7213 Adjudicate(ChessProgramState *cps)
7214 {       // [HGM] some adjudications useful with buggy engines
7215         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7216         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7217         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7218         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7219         int k, count = 0; static int bare = 1;
7220         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7221         Boolean canAdjudicate = !appData.icsActive;
7222
7223         // most tests only when we understand the game, i.e. legality-checking on
7224             if( appData.testLegality )
7225             {   /* [HGM] Some more adjudications for obstinate engines */
7226                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7227                 static int moveCount = 6;
7228                 ChessMove result;
7229                 char *reason = NULL;
7230
7231                 /* Count what is on board. */
7232                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7233
7234                 /* Some material-based adjudications that have to be made before stalemate test */
7235                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7236                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7237                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7238                      if(canAdjudicate && appData.checkMates) {
7239                          if(engineOpponent)
7240                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7241                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7242                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7243                          return 1;
7244                      }
7245                 }
7246
7247                 /* Bare King in Shatranj (loses) or Losers (wins) */
7248                 if( nrW == 1 || nrB == 1) {
7249                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7250                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7251                      if(canAdjudicate && appData.checkMates) {
7252                          if(engineOpponent)
7253                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7254                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7255                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7256                          return 1;
7257                      }
7258                   } else
7259                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7260                   {    /* bare King */
7261                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7262                         if(canAdjudicate && appData.checkMates) {
7263                             /* but only adjudicate if adjudication enabled */
7264                             if(engineOpponent)
7265                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7266                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7267                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7268                             return 1;
7269                         }
7270                   }
7271                 } else bare = 1;
7272
7273
7274             // don't wait for engine to announce game end if we can judge ourselves
7275             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7276               case MT_CHECK:
7277                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7278                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7279                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7280                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7281                             checkCnt++;
7282                         if(checkCnt >= 2) {
7283                             reason = "Xboard adjudication: 3rd check";
7284                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7285                             break;
7286                         }
7287                     }
7288                 }
7289               case MT_NONE:
7290               default:
7291                 break;
7292               case MT_STALEMATE:
7293               case MT_STAINMATE:
7294                 reason = "Xboard adjudication: Stalemate";
7295                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7296                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7297                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7298                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7299                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7300                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7301                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7302                                                                         EP_CHECKMATE : EP_WINS);
7303                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7304                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7305                 }
7306                 break;
7307               case MT_CHECKMATE:
7308                 reason = "Xboard adjudication: Checkmate";
7309                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7310                 break;
7311             }
7312
7313                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7314                     case EP_STALEMATE:
7315                         result = GameIsDrawn; break;
7316                     case EP_CHECKMATE:
7317                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7318                     case EP_WINS:
7319                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7320                     default:
7321                         result = EndOfFile;
7322                 }
7323                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7324                     if(engineOpponent)
7325                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7326                     GameEnds( result, reason, GE_XBOARD );
7327                     return 1;
7328                 }
7329
7330                 /* Next absolutely insufficient mating material. */
7331                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7332                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7333                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7334
7335                      /* always flag draws, for judging claims */
7336                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7337
7338                      if(canAdjudicate && appData.materialDraws) {
7339                          /* but only adjudicate them if adjudication enabled */
7340                          if(engineOpponent) {
7341                            SendToProgram("force\n", engineOpponent); // suppress reply
7342                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7343                          }
7344                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7345                          return 1;
7346                      }
7347                 }
7348
7349                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7350                 if(gameInfo.variant == VariantXiangqi ?
7351                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7352                  : nrW + nrB == 4 &&
7353                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7354                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7355                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7356                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7357                    ) ) {
7358                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7359                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7360                           if(engineOpponent) {
7361                             SendToProgram("force\n", engineOpponent); // suppress reply
7362                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7363                           }
7364                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7365                           return 1;
7366                      }
7367                 } else moveCount = 6;
7368             }
7369         if (appData.debugMode) { int i;
7370             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7371                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7372                     appData.drawRepeats);
7373             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7374               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7375
7376         }
7377
7378         // Repetition draws and 50-move rule can be applied independently of legality testing
7379
7380                 /* Check for rep-draws */
7381                 count = 0;
7382                 for(k = forwardMostMove-2;
7383                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7384                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7385                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7386                     k-=2)
7387                 {   int rights=0;
7388                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7389                         /* compare castling rights */
7390                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7391                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7392                                 rights++; /* King lost rights, while rook still had them */
7393                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7394                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7395                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7396                                    rights++; /* but at least one rook lost them */
7397                         }
7398                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7399                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7400                                 rights++;
7401                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7402                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7403                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7404                                    rights++;
7405                         }
7406                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7407                             && appData.drawRepeats > 1) {
7408                              /* adjudicate after user-specified nr of repeats */
7409                              int result = GameIsDrawn;
7410                              char *details = "XBoard adjudication: repetition draw";
7411                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7412                                 // [HGM] xiangqi: check for forbidden perpetuals
7413                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7414                                 for(m=forwardMostMove; m>k; m-=2) {
7415                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7416                                         ourPerpetual = 0; // the current mover did not always check
7417                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7418                                         hisPerpetual = 0; // the opponent did not always check
7419                                 }
7420                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7421                                                                         ourPerpetual, hisPerpetual);
7422                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7423                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7424                                     details = "Xboard adjudication: perpetual checking";
7425                                 } else
7426                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7427                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7428                                 } else
7429                                 // Now check for perpetual chases
7430                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7431                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7432                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7433                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7434                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7435                                         details = "Xboard adjudication: perpetual chasing";
7436                                     } else
7437                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7438                                         break; // Abort repetition-checking loop.
7439                                 }
7440                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7441                              }
7442                              if(engineOpponent) {
7443                                SendToProgram("force\n", engineOpponent); // suppress reply
7444                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7445                              }
7446                              GameEnds( result, details, GE_XBOARD );
7447                              return 1;
7448                         }
7449                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7450                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7451                     }
7452                 }
7453
7454                 /* Now we test for 50-move draws. Determine ply count */
7455                 count = forwardMostMove;
7456                 /* look for last irreversble move */
7457                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7458                     count--;
7459                 /* if we hit starting position, add initial plies */
7460                 if( count == backwardMostMove )
7461                     count -= initialRulePlies;
7462                 count = forwardMostMove - count;
7463                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7464                         // adjust reversible move counter for checks in Xiangqi
7465                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7466                         if(i < backwardMostMove) i = backwardMostMove;
7467                         while(i <= forwardMostMove) {
7468                                 lastCheck = inCheck; // check evasion does not count
7469                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7470                                 if(inCheck || lastCheck) count--; // check does not count
7471                                 i++;
7472                         }
7473                 }
7474                 if( count >= 100)
7475                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7476                          /* this is used to judge if draw claims are legal */
7477                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7478                          if(engineOpponent) {
7479                            SendToProgram("force\n", engineOpponent); // suppress reply
7480                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7481                          }
7482                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7483                          return 1;
7484                 }
7485
7486                 /* if draw offer is pending, treat it as a draw claim
7487                  * when draw condition present, to allow engines a way to
7488                  * claim draws before making their move to avoid a race
7489                  * condition occurring after their move
7490                  */
7491                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7492                          char *p = NULL;
7493                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7494                              p = "Draw claim: 50-move rule";
7495                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7496                              p = "Draw claim: 3-fold repetition";
7497                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7498                              p = "Draw claim: insufficient mating material";
7499                          if( p != NULL && canAdjudicate) {
7500                              if(engineOpponent) {
7501                                SendToProgram("force\n", engineOpponent); // suppress reply
7502                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7503                              }
7504                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7505                              return 1;
7506                          }
7507                 }
7508
7509                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7510                     if(engineOpponent) {
7511                       SendToProgram("force\n", engineOpponent); // suppress reply
7512                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                     }
7514                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7515                     return 1;
7516                 }
7517         return 0;
7518 }
7519
7520 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7521 {   // [HGM] book: this routine intercepts moves to simulate book replies
7522     char *bookHit = NULL;
7523
7524     //first determine if the incoming move brings opponent into his book
7525     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7526         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7527     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7528     if(bookHit != NULL && !cps->bookSuspend) {
7529         // make sure opponent is not going to reply after receiving move to book position
7530         SendToProgram("force\n", cps);
7531         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7532     }
7533     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7534     // now arrange restart after book miss
7535     if(bookHit) {
7536         // after a book hit we never send 'go', and the code after the call to this routine
7537         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7538         char buf[MSG_SIZ];
7539         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7540         SendToProgram(buf, cps);
7541         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7542     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7543         SendToProgram("go\n", cps);
7544         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7545     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7546         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7547             SendToProgram("go\n", cps);
7548         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7549     }
7550     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7551 }
7552
7553 char *savedMessage;
7554 ChessProgramState *savedState;
7555 void DeferredBookMove(void)
7556 {
7557         if(savedState->lastPing != savedState->lastPong)
7558                     ScheduleDelayedEvent(DeferredBookMove, 10);
7559         else
7560         HandleMachineMove(savedMessage, savedState);
7561 }
7562
7563 void
7564 HandleMachineMove(message, cps)
7565      char *message;
7566      ChessProgramState *cps;
7567 {
7568     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7569     char realname[MSG_SIZ];
7570     int fromX, fromY, toX, toY;
7571     ChessMove moveType;
7572     char promoChar;
7573     char *p;
7574     int machineWhite;
7575     char *bookHit;
7576
7577     cps->userError = 0;
7578
7579 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7580     /*
7581      * Kludge to ignore BEL characters
7582      */
7583     while (*message == '\007') message++;
7584
7585     /*
7586      * [HGM] engine debug message: ignore lines starting with '#' character
7587      */
7588     if(cps->debug && *message == '#') return;
7589
7590     /*
7591      * Look for book output
7592      */
7593     if (cps == &first && bookRequested) {
7594         if (message[0] == '\t' || message[0] == ' ') {
7595             /* Part of the book output is here; append it */
7596             strcat(bookOutput, message);
7597             strcat(bookOutput, "  \n");
7598             return;
7599         } else if (bookOutput[0] != NULLCHAR) {
7600             /* All of book output has arrived; display it */
7601             char *p = bookOutput;
7602             while (*p != NULLCHAR) {
7603                 if (*p == '\t') *p = ' ';
7604                 p++;
7605             }
7606             DisplayInformation(bookOutput);
7607             bookRequested = FALSE;
7608             /* Fall through to parse the current output */
7609         }
7610     }
7611
7612     /*
7613      * Look for machine move.
7614      */
7615     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7616         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7617     {
7618         /* This method is only useful on engines that support ping */
7619         if (cps->lastPing != cps->lastPong) {
7620           if (gameMode == BeginningOfGame) {
7621             /* Extra move from before last new; ignore */
7622             if (appData.debugMode) {
7623                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7624             }
7625           } else {
7626             if (appData.debugMode) {
7627                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7628                         cps->which, gameMode);
7629             }
7630
7631             SendToProgram("undo\n", cps);
7632           }
7633           return;
7634         }
7635
7636         switch (gameMode) {
7637           case BeginningOfGame:
7638             /* Extra move from before last reset; ignore */
7639             if (appData.debugMode) {
7640                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7641             }
7642             return;
7643
7644           case EndOfGame:
7645           case IcsIdle:
7646           default:
7647             /* Extra move after we tried to stop.  The mode test is
7648                not a reliable way of detecting this problem, but it's
7649                the best we can do on engines that don't support ping.
7650             */
7651             if (appData.debugMode) {
7652                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7653                         cps->which, gameMode);
7654             }
7655             SendToProgram("undo\n", cps);
7656             return;
7657
7658           case MachinePlaysWhite:
7659           case IcsPlayingWhite:
7660             machineWhite = TRUE;
7661             break;
7662
7663           case MachinePlaysBlack:
7664           case IcsPlayingBlack:
7665             machineWhite = FALSE;
7666             break;
7667
7668           case TwoMachinesPlay:
7669             machineWhite = (cps->twoMachinesColor[0] == 'w');
7670             break;
7671         }
7672         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7673             if (appData.debugMode) {
7674                 fprintf(debugFP,
7675                         "Ignoring move out of turn by %s, gameMode %d"
7676                         ", forwardMost %d\n",
7677                         cps->which, gameMode, forwardMostMove);
7678             }
7679             return;
7680         }
7681
7682     if (appData.debugMode) { int f = forwardMostMove;
7683         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7684                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7685                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7686     }
7687         if(cps->alphaRank) AlphaRank(machineMove, 4);
7688         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7689                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7690             /* Machine move could not be parsed; ignore it. */
7691           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7692                     machineMove, _(cps->which));
7693             DisplayError(buf1, 0);
7694             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7695                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7696             if (gameMode == TwoMachinesPlay) {
7697               GameEnds(machineWhite ? BlackWins : WhiteWins,
7698                        buf1, GE_XBOARD);
7699             }
7700             return;
7701         }
7702
7703         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7704         /* So we have to redo legality test with true e.p. status here,  */
7705         /* to make sure an illegal e.p. capture does not slip through,   */
7706         /* to cause a forfeit on a justified illegal-move complaint      */
7707         /* of the opponent.                                              */
7708         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7709            ChessMove moveType;
7710            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7711                              fromY, fromX, toY, toX, promoChar);
7712             if (appData.debugMode) {
7713                 int i;
7714                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7715                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7716                 fprintf(debugFP, "castling rights\n");
7717             }
7718             if(moveType == IllegalMove) {
7719               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7720                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7721                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7722                            buf1, GE_XBOARD);
7723                 return;
7724            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7725            /* [HGM] Kludge to handle engines that send FRC-style castling
7726               when they shouldn't (like TSCP-Gothic) */
7727            switch(moveType) {
7728              case WhiteASideCastleFR:
7729              case BlackASideCastleFR:
7730                toX+=2;
7731                currentMoveString[2]++;
7732                break;
7733              case WhiteHSideCastleFR:
7734              case BlackHSideCastleFR:
7735                toX--;
7736                currentMoveString[2]--;
7737                break;
7738              default: ; // nothing to do, but suppresses warning of pedantic compilers
7739            }
7740         }
7741         hintRequested = FALSE;
7742         lastHint[0] = NULLCHAR;
7743         bookRequested = FALSE;
7744         /* Program may be pondering now */
7745         cps->maybeThinking = TRUE;
7746         if (cps->sendTime == 2) cps->sendTime = 1;
7747         if (cps->offeredDraw) cps->offeredDraw--;
7748
7749         /* [AS] Save move info*/
7750         pvInfoList[ forwardMostMove ].score = programStats.score;
7751         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7752         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7753
7754         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7755
7756         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7757         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7758             int count = 0;
7759
7760             while( count < adjudicateLossPlies ) {
7761                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7762
7763                 if( count & 1 ) {
7764                     score = -score; /* Flip score for winning side */
7765                 }
7766
7767                 if( score > adjudicateLossThreshold ) {
7768                     break;
7769                 }
7770
7771                 count++;
7772             }
7773
7774             if( count >= adjudicateLossPlies ) {
7775                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7776
7777                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7778                     "Xboard adjudication",
7779                     GE_XBOARD );
7780
7781                 return;
7782             }
7783         }
7784
7785         if(Adjudicate(cps)) {
7786             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7787             return; // [HGM] adjudicate: for all automatic game ends
7788         }
7789
7790 #if ZIPPY
7791         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7792             first.initDone) {
7793           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7794                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7795                 SendToICS("draw ");
7796                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7797           }
7798           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7799           ics_user_moved = 1;
7800           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7801                 char buf[3*MSG_SIZ];
7802
7803                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7804                         programStats.score / 100.,
7805                         programStats.depth,
7806                         programStats.time / 100.,
7807                         (unsigned int)programStats.nodes,
7808                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7809                         programStats.movelist);
7810                 SendToICS(buf);
7811 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7812           }
7813         }
7814 #endif
7815
7816         /* [AS] Clear stats for next move */
7817         ClearProgramStats();
7818         thinkOutput[0] = NULLCHAR;
7819         hiddenThinkOutputState = 0;
7820
7821         bookHit = NULL;
7822         if (gameMode == TwoMachinesPlay) {
7823             /* [HGM] relaying draw offers moved to after reception of move */
7824             /* and interpreting offer as claim if it brings draw condition */
7825             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7826                 SendToProgram("draw\n", cps->other);
7827             }
7828             if (cps->other->sendTime) {
7829                 SendTimeRemaining(cps->other,
7830                                   cps->other->twoMachinesColor[0] == 'w');
7831             }
7832             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7833             if (firstMove && !bookHit) {
7834                 firstMove = FALSE;
7835                 if (cps->other->useColors) {
7836                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7837                 }
7838                 SendToProgram("go\n", cps->other);
7839             }
7840             cps->other->maybeThinking = TRUE;
7841         }
7842
7843         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7844
7845         if (!pausing && appData.ringBellAfterMoves) {
7846             RingBell();
7847         }
7848
7849         /*
7850          * Reenable menu items that were disabled while
7851          * machine was thinking
7852          */
7853         if (gameMode != TwoMachinesPlay)
7854             SetUserThinkingEnables();
7855
7856         // [HGM] book: after book hit opponent has received move and is now in force mode
7857         // force the book reply into it, and then fake that it outputted this move by jumping
7858         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7859         if(bookHit) {
7860                 static char bookMove[MSG_SIZ]; // a bit generous?
7861
7862                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7863                 strcat(bookMove, bookHit);
7864                 message = bookMove;
7865                 cps = cps->other;
7866                 programStats.nodes = programStats.depth = programStats.time =
7867                 programStats.score = programStats.got_only_move = 0;
7868                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7869
7870                 if(cps->lastPing != cps->lastPong) {
7871                     savedMessage = message; // args for deferred call
7872                     savedState = cps;
7873                     ScheduleDelayedEvent(DeferredBookMove, 10);
7874                     return;
7875                 }
7876                 goto FakeBookMove;
7877         }
7878
7879         return;
7880     }
7881
7882     /* Set special modes for chess engines.  Later something general
7883      *  could be added here; for now there is just one kludge feature,
7884      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7885      *  when "xboard" is given as an interactive command.
7886      */
7887     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7888         cps->useSigint = FALSE;
7889         cps->useSigterm = FALSE;
7890     }
7891     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7892       ParseFeatures(message+8, cps);
7893       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7894     }
7895
7896     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7897       int dummy, s=6; char buf[MSG_SIZ];
7898       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7899       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7900       ParseFEN(boards[0], &dummy, message+s);
7901       DrawPosition(TRUE, boards[0]);
7902       startedFromSetupPosition = TRUE;
7903       return;
7904     }
7905     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7906      * want this, I was asked to put it in, and obliged.
7907      */
7908     if (!strncmp(message, "setboard ", 9)) {
7909         Board initial_position;
7910
7911         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7912
7913         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7914             DisplayError(_("Bad FEN received from engine"), 0);
7915             return ;
7916         } else {
7917            Reset(TRUE, FALSE);
7918            CopyBoard(boards[0], initial_position);
7919            initialRulePlies = FENrulePlies;
7920            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7921            else gameMode = MachinePlaysBlack;
7922            DrawPosition(FALSE, boards[currentMove]);
7923         }
7924         return;
7925     }
7926
7927     /*
7928      * Look for communication commands
7929      */
7930     if (!strncmp(message, "telluser ", 9)) {
7931         if(message[9] == '\\' && message[10] == '\\')
7932             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7933         DisplayNote(message + 9);
7934         return;
7935     }
7936     if (!strncmp(message, "tellusererror ", 14)) {
7937         cps->userError = 1;
7938         if(message[14] == '\\' && message[15] == '\\')
7939             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7940         DisplayError(message + 14, 0);
7941         return;
7942     }
7943     if (!strncmp(message, "tellopponent ", 13)) {
7944       if (appData.icsActive) {
7945         if (loggedOn) {
7946           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7947           SendToICS(buf1);
7948         }
7949       } else {
7950         DisplayNote(message + 13);
7951       }
7952       return;
7953     }
7954     if (!strncmp(message, "tellothers ", 11)) {
7955       if (appData.icsActive) {
7956         if (loggedOn) {
7957           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7958           SendToICS(buf1);
7959         }
7960       }
7961       return;
7962     }
7963     if (!strncmp(message, "tellall ", 8)) {
7964       if (appData.icsActive) {
7965         if (loggedOn) {
7966           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7967           SendToICS(buf1);
7968         }
7969       } else {
7970         DisplayNote(message + 8);
7971       }
7972       return;
7973     }
7974     if (strncmp(message, "warning", 7) == 0) {
7975         /* Undocumented feature, use tellusererror in new code */
7976         DisplayError(message, 0);
7977         return;
7978     }
7979     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7980         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7981         strcat(realname, " query");
7982         AskQuestion(realname, buf2, buf1, cps->pr);
7983         return;
7984     }
7985     /* Commands from the engine directly to ICS.  We don't allow these to be
7986      *  sent until we are logged on. Crafty kibitzes have been known to
7987      *  interfere with the login process.
7988      */
7989     if (loggedOn) {
7990         if (!strncmp(message, "tellics ", 8)) {
7991             SendToICS(message + 8);
7992             SendToICS("\n");
7993             return;
7994         }
7995         if (!strncmp(message, "tellicsnoalias ", 15)) {
7996             SendToICS(ics_prefix);
7997             SendToICS(message + 15);
7998             SendToICS("\n");
7999             return;
8000         }
8001         /* The following are for backward compatibility only */
8002         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8003             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8004             SendToICS(ics_prefix);
8005             SendToICS(message);
8006             SendToICS("\n");
8007             return;
8008         }
8009     }
8010     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8011         return;
8012     }
8013     /*
8014      * If the move is illegal, cancel it and redraw the board.
8015      * Also deal with other error cases.  Matching is rather loose
8016      * here to accommodate engines written before the spec.
8017      */
8018     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8019         strncmp(message, "Error", 5) == 0) {
8020         if (StrStr(message, "name") ||
8021             StrStr(message, "rating") || StrStr(message, "?") ||
8022             StrStr(message, "result") || StrStr(message, "board") ||
8023             StrStr(message, "bk") || StrStr(message, "computer") ||
8024             StrStr(message, "variant") || StrStr(message, "hint") ||
8025             StrStr(message, "random") || StrStr(message, "depth") ||
8026             StrStr(message, "accepted")) {
8027             return;
8028         }
8029         if (StrStr(message, "protover")) {
8030           /* Program is responding to input, so it's apparently done
8031              initializing, and this error message indicates it is
8032              protocol version 1.  So we don't need to wait any longer
8033              for it to initialize and send feature commands. */
8034           FeatureDone(cps, 1);
8035           cps->protocolVersion = 1;
8036           return;
8037         }
8038         cps->maybeThinking = FALSE;
8039
8040         if (StrStr(message, "draw")) {
8041             /* Program doesn't have "draw" command */
8042             cps->sendDrawOffers = 0;
8043             return;
8044         }
8045         if (cps->sendTime != 1 &&
8046             (StrStr(message, "time") || StrStr(message, "otim"))) {
8047           /* Program apparently doesn't have "time" or "otim" command */
8048           cps->sendTime = 0;
8049           return;
8050         }
8051         if (StrStr(message, "analyze")) {
8052             cps->analysisSupport = FALSE;
8053             cps->analyzing = FALSE;
8054             Reset(FALSE, TRUE);
8055             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8056             DisplayError(buf2, 0);
8057             return;
8058         }
8059         if (StrStr(message, "(no matching move)st")) {
8060           /* Special kludge for GNU Chess 4 only */
8061           cps->stKludge = TRUE;
8062           SendTimeControl(cps, movesPerSession, timeControl,
8063                           timeIncrement, appData.searchDepth,
8064                           searchTime);
8065           return;
8066         }
8067         if (StrStr(message, "(no matching move)sd")) {
8068           /* Special kludge for GNU Chess 4 only */
8069           cps->sdKludge = TRUE;
8070           SendTimeControl(cps, movesPerSession, timeControl,
8071                           timeIncrement, appData.searchDepth,
8072                           searchTime);
8073           return;
8074         }
8075         if (!StrStr(message, "llegal")) {
8076             return;
8077         }
8078         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8079             gameMode == IcsIdle) return;
8080         if (forwardMostMove <= backwardMostMove) return;
8081         if (pausing) PauseEvent();
8082       if(appData.forceIllegal) {
8083             // [HGM] illegal: machine refused move; force position after move into it
8084           SendToProgram("force\n", cps);
8085           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8086                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8087                 // when black is to move, while there might be nothing on a2 or black
8088                 // might already have the move. So send the board as if white has the move.
8089                 // But first we must change the stm of the engine, as it refused the last move
8090                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8091                 if(WhiteOnMove(forwardMostMove)) {
8092                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8093                     SendBoard(cps, forwardMostMove); // kludgeless board
8094                 } else {
8095                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8096                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8097                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8098                 }
8099           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8100             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8101                  gameMode == TwoMachinesPlay)
8102               SendToProgram("go\n", cps);
8103             return;
8104       } else
8105         if (gameMode == PlayFromGameFile) {
8106             /* Stop reading this game file */
8107             gameMode = EditGame;
8108             ModeHighlight();
8109         }
8110         /* [HGM] illegal-move claim should forfeit game when Xboard */
8111         /* only passes fully legal moves                            */
8112         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8113             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8114                                 "False illegal-move claim", GE_XBOARD );
8115             return; // do not take back move we tested as valid
8116         }
8117         currentMove = forwardMostMove-1;
8118         DisplayMove(currentMove-1); /* before DisplayMoveError */
8119         SwitchClocks(forwardMostMove-1); // [HGM] race
8120         DisplayBothClocks();
8121         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8122                 parseList[currentMove], _(cps->which));
8123         DisplayMoveError(buf1);
8124         DrawPosition(FALSE, boards[currentMove]);
8125         return;
8126     }
8127     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8128         /* Program has a broken "time" command that
8129            outputs a string not ending in newline.
8130            Don't use it. */
8131         cps->sendTime = 0;
8132     }
8133
8134     /*
8135      * If chess program startup fails, exit with an error message.
8136      * Attempts to recover here are futile.
8137      */
8138     if ((StrStr(message, "unknown host") != NULL)
8139         || (StrStr(message, "No remote directory") != NULL)
8140         || (StrStr(message, "not found") != NULL)
8141         || (StrStr(message, "No such file") != NULL)
8142         || (StrStr(message, "can't alloc") != NULL)
8143         || (StrStr(message, "Permission denied") != NULL)) {
8144
8145         cps->maybeThinking = FALSE;
8146         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8147                 _(cps->which), cps->program, cps->host, message);
8148         RemoveInputSource(cps->isr);
8149         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8150             if(cps == &first) appData.noChessProgram = TRUE;
8151             DisplayError(buf1, 0);
8152         }
8153         return;
8154     }
8155
8156     /*
8157      * Look for hint output
8158      */
8159     if (sscanf(message, "Hint: %s", buf1) == 1) {
8160         if (cps == &first && hintRequested) {
8161             hintRequested = FALSE;
8162             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8163                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8164                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8165                                     PosFlags(forwardMostMove),
8166                                     fromY, fromX, toY, toX, promoChar, buf1);
8167                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8168                 DisplayInformation(buf2);
8169             } else {
8170                 /* Hint move could not be parsed!? */
8171               snprintf(buf2, sizeof(buf2),
8172                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8173                         buf1, _(cps->which));
8174                 DisplayError(buf2, 0);
8175             }
8176         } else {
8177           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8178         }
8179         return;
8180     }
8181
8182     /*
8183      * Ignore other messages if game is not in progress
8184      */
8185     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8186         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8187
8188     /*
8189      * look for win, lose, draw, or draw offer
8190      */
8191     if (strncmp(message, "1-0", 3) == 0) {
8192         char *p, *q, *r = "";
8193         p = strchr(message, '{');
8194         if (p) {
8195             q = strchr(p, '}');
8196             if (q) {
8197                 *q = NULLCHAR;
8198                 r = p + 1;
8199             }
8200         }
8201         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8202         return;
8203     } else if (strncmp(message, "0-1", 3) == 0) {
8204         char *p, *q, *r = "";
8205         p = strchr(message, '{');
8206         if (p) {
8207             q = strchr(p, '}');
8208             if (q) {
8209                 *q = NULLCHAR;
8210                 r = p + 1;
8211             }
8212         }
8213         /* Kludge for Arasan 4.1 bug */
8214         if (strcmp(r, "Black resigns") == 0) {
8215             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8216             return;
8217         }
8218         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8219         return;
8220     } else if (strncmp(message, "1/2", 3) == 0) {
8221         char *p, *q, *r = "";
8222         p = strchr(message, '{');
8223         if (p) {
8224             q = strchr(p, '}');
8225             if (q) {
8226                 *q = NULLCHAR;
8227                 r = p + 1;
8228             }
8229         }
8230
8231         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8232         return;
8233
8234     } else if (strncmp(message, "White resign", 12) == 0) {
8235         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8236         return;
8237     } else if (strncmp(message, "Black resign", 12) == 0) {
8238         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8239         return;
8240     } else if (strncmp(message, "White matches", 13) == 0 ||
8241                strncmp(message, "Black matches", 13) == 0   ) {
8242         /* [HGM] ignore GNUShogi noises */
8243         return;
8244     } else if (strncmp(message, "White", 5) == 0 &&
8245                message[5] != '(' &&
8246                StrStr(message, "Black") == NULL) {
8247         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8248         return;
8249     } else if (strncmp(message, "Black", 5) == 0 &&
8250                message[5] != '(') {
8251         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8252         return;
8253     } else if (strcmp(message, "resign") == 0 ||
8254                strcmp(message, "computer resigns") == 0) {
8255         switch (gameMode) {
8256           case MachinePlaysBlack:
8257           case IcsPlayingBlack:
8258             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8259             break;
8260           case MachinePlaysWhite:
8261           case IcsPlayingWhite:
8262             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8263             break;
8264           case TwoMachinesPlay:
8265             if (cps->twoMachinesColor[0] == 'w')
8266               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8267             else
8268               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8269             break;
8270           default:
8271             /* can't happen */
8272             break;
8273         }
8274         return;
8275     } else if (strncmp(message, "opponent mates", 14) == 0) {
8276         switch (gameMode) {
8277           case MachinePlaysBlack:
8278           case IcsPlayingBlack:
8279             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8280             break;
8281           case MachinePlaysWhite:
8282           case IcsPlayingWhite:
8283             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8284             break;
8285           case TwoMachinesPlay:
8286             if (cps->twoMachinesColor[0] == 'w')
8287               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8288             else
8289               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8290             break;
8291           default:
8292             /* can't happen */
8293             break;
8294         }
8295         return;
8296     } else if (strncmp(message, "computer mates", 14) == 0) {
8297         switch (gameMode) {
8298           case MachinePlaysBlack:
8299           case IcsPlayingBlack:
8300             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8301             break;
8302           case MachinePlaysWhite:
8303           case IcsPlayingWhite:
8304             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8305             break;
8306           case TwoMachinesPlay:
8307             if (cps->twoMachinesColor[0] == 'w')
8308               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8309             else
8310               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8311             break;
8312           default:
8313             /* can't happen */
8314             break;
8315         }
8316         return;
8317     } else if (strncmp(message, "checkmate", 9) == 0) {
8318         if (WhiteOnMove(forwardMostMove)) {
8319             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8320         } else {
8321             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8322         }
8323         return;
8324     } else if (strstr(message, "Draw") != NULL ||
8325                strstr(message, "game is a draw") != NULL) {
8326         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8327         return;
8328     } else if (strstr(message, "offer") != NULL &&
8329                strstr(message, "draw") != NULL) {
8330 #if ZIPPY
8331         if (appData.zippyPlay && first.initDone) {
8332             /* Relay offer to ICS */
8333             SendToICS(ics_prefix);
8334             SendToICS("draw\n");
8335         }
8336 #endif
8337         cps->offeredDraw = 2; /* valid until this engine moves twice */
8338         if (gameMode == TwoMachinesPlay) {
8339             if (cps->other->offeredDraw) {
8340                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8341             /* [HGM] in two-machine mode we delay relaying draw offer      */
8342             /* until after we also have move, to see if it is really claim */
8343             }
8344         } else if (gameMode == MachinePlaysWhite ||
8345                    gameMode == MachinePlaysBlack) {
8346           if (userOfferedDraw) {
8347             DisplayInformation(_("Machine accepts your draw offer"));
8348             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8349           } else {
8350             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8351           }
8352         }
8353     }
8354
8355
8356     /*
8357      * Look for thinking output
8358      */
8359     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8360           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8361                                 ) {
8362         int plylev, mvleft, mvtot, curscore, time;
8363         char mvname[MOVE_LEN];
8364         u64 nodes; // [DM]
8365         char plyext;
8366         int ignore = FALSE;
8367         int prefixHint = FALSE;
8368         mvname[0] = NULLCHAR;
8369
8370         switch (gameMode) {
8371           case MachinePlaysBlack:
8372           case IcsPlayingBlack:
8373             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8374             break;
8375           case MachinePlaysWhite:
8376           case IcsPlayingWhite:
8377             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8378             break;
8379           case AnalyzeMode:
8380           case AnalyzeFile:
8381             break;
8382           case IcsObserving: /* [DM] icsEngineAnalyze */
8383             if (!appData.icsEngineAnalyze) ignore = TRUE;
8384             break;
8385           case TwoMachinesPlay:
8386             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8387                 ignore = TRUE;
8388             }
8389             break;
8390           default:
8391             ignore = TRUE;
8392             break;
8393         }
8394
8395         if (!ignore) {
8396             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8397             buf1[0] = NULLCHAR;
8398             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8399                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8400
8401                 if (plyext != ' ' && plyext != '\t') {
8402                     time *= 100;
8403                 }
8404
8405                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8406                 if( cps->scoreIsAbsolute &&
8407                     ( gameMode == MachinePlaysBlack ||
8408                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8409                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8410                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8411                      !WhiteOnMove(currentMove)
8412                     ) )
8413                 {
8414                     curscore = -curscore;
8415                 }
8416
8417
8418                 tempStats.depth = plylev;
8419                 tempStats.nodes = nodes;
8420                 tempStats.time = time;
8421                 tempStats.score = curscore;
8422                 tempStats.got_only_move = 0;
8423
8424                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8425                         int ticklen;
8426
8427                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8428                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8429                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8430                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8431                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8432                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8433                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8434                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8435                 }
8436
8437                 /* Buffer overflow protection */
8438                 if (buf1[0] != NULLCHAR) {
8439                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8440                         && appData.debugMode) {
8441                         fprintf(debugFP,
8442                                 "PV is too long; using the first %u bytes.\n",
8443                                 (unsigned) sizeof(tempStats.movelist) - 1);
8444                     }
8445
8446                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8447                 } else {
8448                     sprintf(tempStats.movelist, " no PV\n");
8449                 }
8450
8451                 if (tempStats.seen_stat) {
8452                     tempStats.ok_to_send = 1;
8453                 }
8454
8455                 if (strchr(tempStats.movelist, '(') != NULL) {
8456                     tempStats.line_is_book = 1;
8457                     tempStats.nr_moves = 0;
8458                     tempStats.moves_left = 0;
8459                 } else {
8460                     tempStats.line_is_book = 0;
8461                 }
8462
8463                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8464                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8465
8466                 SendProgramStatsToFrontend( cps, &tempStats );
8467
8468                 /*
8469                     [AS] Protect the thinkOutput buffer from overflow... this
8470                     is only useful if buf1 hasn't overflowed first!
8471                 */
8472                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8473                          plylev,
8474                          (gameMode == TwoMachinesPlay ?
8475                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8476                          ((double) curscore) / 100.0,
8477                          prefixHint ? lastHint : "",
8478                          prefixHint ? " " : "" );
8479
8480                 if( buf1[0] != NULLCHAR ) {
8481                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8482
8483                     if( strlen(buf1) > max_len ) {
8484                         if( appData.debugMode) {
8485                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8486                         }
8487                         buf1[max_len+1] = '\0';
8488                     }
8489
8490                     strcat( thinkOutput, buf1 );
8491                 }
8492
8493                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8494                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8495                     DisplayMove(currentMove - 1);
8496                 }
8497                 return;
8498
8499             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8500                 /* crafty (9.25+) says "(only move) <move>"
8501                  * if there is only 1 legal move
8502                  */
8503                 sscanf(p, "(only move) %s", buf1);
8504                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8505                 sprintf(programStats.movelist, "%s (only move)", buf1);
8506                 programStats.depth = 1;
8507                 programStats.nr_moves = 1;
8508                 programStats.moves_left = 1;
8509                 programStats.nodes = 1;
8510                 programStats.time = 1;
8511                 programStats.got_only_move = 1;
8512
8513                 /* Not really, but we also use this member to
8514                    mean "line isn't going to change" (Crafty
8515                    isn't searching, so stats won't change) */
8516                 programStats.line_is_book = 1;
8517
8518                 SendProgramStatsToFrontend( cps, &programStats );
8519
8520                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8521                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8522                     DisplayMove(currentMove - 1);
8523                 }
8524                 return;
8525             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8526                               &time, &nodes, &plylev, &mvleft,
8527                               &mvtot, mvname) >= 5) {
8528                 /* The stat01: line is from Crafty (9.29+) in response
8529                    to the "." command */
8530                 programStats.seen_stat = 1;
8531                 cps->maybeThinking = TRUE;
8532
8533                 if (programStats.got_only_move || !appData.periodicUpdates)
8534                   return;
8535
8536                 programStats.depth = plylev;
8537                 programStats.time = time;
8538                 programStats.nodes = nodes;
8539                 programStats.moves_left = mvleft;
8540                 programStats.nr_moves = mvtot;
8541                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8542                 programStats.ok_to_send = 1;
8543                 programStats.movelist[0] = '\0';
8544
8545                 SendProgramStatsToFrontend( cps, &programStats );
8546
8547                 return;
8548
8549             } else if (strncmp(message,"++",2) == 0) {
8550                 /* Crafty 9.29+ outputs this */
8551                 programStats.got_fail = 2;
8552                 return;
8553
8554             } else if (strncmp(message,"--",2) == 0) {
8555                 /* Crafty 9.29+ outputs this */
8556                 programStats.got_fail = 1;
8557                 return;
8558
8559             } else if (thinkOutput[0] != NULLCHAR &&
8560                        strncmp(message, "    ", 4) == 0) {
8561                 unsigned message_len;
8562
8563                 p = message;
8564                 while (*p && *p == ' ') p++;
8565
8566                 message_len = strlen( p );
8567
8568                 /* [AS] Avoid buffer overflow */
8569                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8570                     strcat(thinkOutput, " ");
8571                     strcat(thinkOutput, p);
8572                 }
8573
8574                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8575                     strcat(programStats.movelist, " ");
8576                     strcat(programStats.movelist, p);
8577                 }
8578
8579                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8580                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8581                     DisplayMove(currentMove - 1);
8582                 }
8583                 return;
8584             }
8585         }
8586         else {
8587             buf1[0] = NULLCHAR;
8588
8589             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8590                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8591             {
8592                 ChessProgramStats cpstats;
8593
8594                 if (plyext != ' ' && plyext != '\t') {
8595                     time *= 100;
8596                 }
8597
8598                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8599                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8600                     curscore = -curscore;
8601                 }
8602
8603                 cpstats.depth = plylev;
8604                 cpstats.nodes = nodes;
8605                 cpstats.time = time;
8606                 cpstats.score = curscore;
8607                 cpstats.got_only_move = 0;
8608                 cpstats.movelist[0] = '\0';
8609
8610                 if (buf1[0] != NULLCHAR) {
8611                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8612                 }
8613
8614                 cpstats.ok_to_send = 0;
8615                 cpstats.line_is_book = 0;
8616                 cpstats.nr_moves = 0;
8617                 cpstats.moves_left = 0;
8618
8619                 SendProgramStatsToFrontend( cps, &cpstats );
8620             }
8621         }
8622     }
8623 }
8624
8625
8626 /* Parse a game score from the character string "game", and
8627    record it as the history of the current game.  The game
8628    score is NOT assumed to start from the standard position.
8629    The display is not updated in any way.
8630    */
8631 void
8632 ParseGameHistory(game)
8633      char *game;
8634 {
8635     ChessMove moveType;
8636     int fromX, fromY, toX, toY, boardIndex;
8637     char promoChar;
8638     char *p, *q;
8639     char buf[MSG_SIZ];
8640
8641     if (appData.debugMode)
8642       fprintf(debugFP, "Parsing game history: %s\n", game);
8643
8644     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8645     gameInfo.site = StrSave(appData.icsHost);
8646     gameInfo.date = PGNDate();
8647     gameInfo.round = StrSave("-");
8648
8649     /* Parse out names of players */
8650     while (*game == ' ') game++;
8651     p = buf;
8652     while (*game != ' ') *p++ = *game++;
8653     *p = NULLCHAR;
8654     gameInfo.white = StrSave(buf);
8655     while (*game == ' ') game++;
8656     p = buf;
8657     while (*game != ' ' && *game != '\n') *p++ = *game++;
8658     *p = NULLCHAR;
8659     gameInfo.black = StrSave(buf);
8660
8661     /* Parse moves */
8662     boardIndex = blackPlaysFirst ? 1 : 0;
8663     yynewstr(game);
8664     for (;;) {
8665         yyboardindex = boardIndex;
8666         moveType = (ChessMove) Myylex();
8667         switch (moveType) {
8668           case IllegalMove:             /* maybe suicide chess, etc. */
8669   if (appData.debugMode) {
8670     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8671     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8672     setbuf(debugFP, NULL);
8673   }
8674           case WhitePromotion:
8675           case BlackPromotion:
8676           case WhiteNonPromotion:
8677           case BlackNonPromotion:
8678           case NormalMove:
8679           case WhiteCapturesEnPassant:
8680           case BlackCapturesEnPassant:
8681           case WhiteKingSideCastle:
8682           case WhiteQueenSideCastle:
8683           case BlackKingSideCastle:
8684           case BlackQueenSideCastle:
8685           case WhiteKingSideCastleWild:
8686           case WhiteQueenSideCastleWild:
8687           case BlackKingSideCastleWild:
8688           case BlackQueenSideCastleWild:
8689           /* PUSH Fabien */
8690           case WhiteHSideCastleFR:
8691           case WhiteASideCastleFR:
8692           case BlackHSideCastleFR:
8693           case BlackASideCastleFR:
8694           /* POP Fabien */
8695             fromX = currentMoveString[0] - AAA;
8696             fromY = currentMoveString[1] - ONE;
8697             toX = currentMoveString[2] - AAA;
8698             toY = currentMoveString[3] - ONE;
8699             promoChar = currentMoveString[4];
8700             break;
8701           case WhiteDrop:
8702           case BlackDrop:
8703             fromX = moveType == WhiteDrop ?
8704               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8705             (int) CharToPiece(ToLower(currentMoveString[0]));
8706             fromY = DROP_RANK;
8707             toX = currentMoveString[2] - AAA;
8708             toY = currentMoveString[3] - ONE;
8709             promoChar = NULLCHAR;
8710             break;
8711           case AmbiguousMove:
8712             /* bug? */
8713             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8714   if (appData.debugMode) {
8715     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8716     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8717     setbuf(debugFP, NULL);
8718   }
8719             DisplayError(buf, 0);
8720             return;
8721           case ImpossibleMove:
8722             /* bug? */
8723             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8724   if (appData.debugMode) {
8725     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8726     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8727     setbuf(debugFP, NULL);
8728   }
8729             DisplayError(buf, 0);
8730             return;
8731           case EndOfFile:
8732             if (boardIndex < backwardMostMove) {
8733                 /* Oops, gap.  How did that happen? */
8734                 DisplayError(_("Gap in move list"), 0);
8735                 return;
8736             }
8737             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8738             if (boardIndex > forwardMostMove) {
8739                 forwardMostMove = boardIndex;
8740             }
8741             return;
8742           case ElapsedTime:
8743             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8744                 strcat(parseList[boardIndex-1], " ");
8745                 strcat(parseList[boardIndex-1], yy_text);
8746             }
8747             continue;
8748           case Comment:
8749           case PGNTag:
8750           case NAG:
8751           default:
8752             /* ignore */
8753             continue;
8754           case WhiteWins:
8755           case BlackWins:
8756           case GameIsDrawn:
8757           case GameUnfinished:
8758             if (gameMode == IcsExamining) {
8759                 if (boardIndex < backwardMostMove) {
8760                     /* Oops, gap.  How did that happen? */
8761                     return;
8762                 }
8763                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8764                 return;
8765             }
8766             gameInfo.result = moveType;
8767             p = strchr(yy_text, '{');
8768             if (p == NULL) p = strchr(yy_text, '(');
8769             if (p == NULL) {
8770                 p = yy_text;
8771                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8772             } else {
8773                 q = strchr(p, *p == '{' ? '}' : ')');
8774                 if (q != NULL) *q = NULLCHAR;
8775                 p++;
8776             }
8777             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8778             gameInfo.resultDetails = StrSave(p);
8779             continue;
8780         }
8781         if (boardIndex >= forwardMostMove &&
8782             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8783             backwardMostMove = blackPlaysFirst ? 1 : 0;
8784             return;
8785         }
8786         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8787                                  fromY, fromX, toY, toX, promoChar,
8788                                  parseList[boardIndex]);
8789         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8790         /* currentMoveString is set as a side-effect of yylex */
8791         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8792         strcat(moveList[boardIndex], "\n");
8793         boardIndex++;
8794         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8795         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8796           case MT_NONE:
8797           case MT_STALEMATE:
8798           default:
8799             break;
8800           case MT_CHECK:
8801             if(gameInfo.variant != VariantShogi)
8802                 strcat(parseList[boardIndex - 1], "+");
8803             break;
8804           case MT_CHECKMATE:
8805           case MT_STAINMATE:
8806             strcat(parseList[boardIndex - 1], "#");
8807             break;
8808         }
8809     }
8810 }
8811
8812
8813 /* Apply a move to the given board  */
8814 void
8815 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8816      int fromX, fromY, toX, toY;
8817      int promoChar;
8818      Board board;
8819 {
8820   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8821   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8822
8823     /* [HGM] compute & store e.p. status and castling rights for new position */
8824     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8825
8826       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8827       oldEP = (signed char)board[EP_STATUS];
8828       board[EP_STATUS] = EP_NONE;
8829
8830       if( board[toY][toX] != EmptySquare )
8831            board[EP_STATUS] = EP_CAPTURE;
8832
8833   if (fromY == DROP_RANK) {
8834         /* must be first */
8835         piece = board[toY][toX] = (ChessSquare) fromX;
8836   } else {
8837       int i;
8838
8839       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8840            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8841                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8842       } else
8843       if( board[fromY][fromX] == WhitePawn ) {
8844            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8845                board[EP_STATUS] = EP_PAWN_MOVE;
8846            if( toY-fromY==2) {
8847                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8848                         gameInfo.variant != VariantBerolina || toX < fromX)
8849                       board[EP_STATUS] = toX | berolina;
8850                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8851                         gameInfo.variant != VariantBerolina || toX > fromX)
8852                       board[EP_STATUS] = toX;
8853            }
8854       } else
8855       if( board[fromY][fromX] == BlackPawn ) {
8856            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8857                board[EP_STATUS] = EP_PAWN_MOVE;
8858            if( toY-fromY== -2) {
8859                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8860                         gameInfo.variant != VariantBerolina || toX < fromX)
8861                       board[EP_STATUS] = toX | berolina;
8862                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8863                         gameInfo.variant != VariantBerolina || toX > fromX)
8864                       board[EP_STATUS] = toX;
8865            }
8866        }
8867
8868        for(i=0; i<nrCastlingRights; i++) {
8869            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8870               board[CASTLING][i] == toX   && castlingRank[i] == toY
8871              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8872        }
8873
8874      if (fromX == toX && fromY == toY) return;
8875
8876      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8877      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8878      if(gameInfo.variant == VariantKnightmate)
8879          king += (int) WhiteUnicorn - (int) WhiteKing;
8880
8881     /* Code added by Tord: */
8882     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8883     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8884         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8885       board[fromY][fromX] = EmptySquare;
8886       board[toY][toX] = EmptySquare;
8887       if((toX > fromX) != (piece == WhiteRook)) {
8888         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8889       } else {
8890         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8891       }
8892     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8893                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8894       board[fromY][fromX] = EmptySquare;
8895       board[toY][toX] = EmptySquare;
8896       if((toX > fromX) != (piece == BlackRook)) {
8897         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8898       } else {
8899         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8900       }
8901     /* End of code added by Tord */
8902
8903     } else if (board[fromY][fromX] == king
8904         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8905         && toY == fromY && toX > fromX+1) {
8906         board[fromY][fromX] = EmptySquare;
8907         board[toY][toX] = king;
8908         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8909         board[fromY][BOARD_RGHT-1] = EmptySquare;
8910     } else if (board[fromY][fromX] == king
8911         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8912                && toY == fromY && toX < fromX-1) {
8913         board[fromY][fromX] = EmptySquare;
8914         board[toY][toX] = king;
8915         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8916         board[fromY][BOARD_LEFT] = EmptySquare;
8917     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8918                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8919                && toY >= BOARD_HEIGHT-promoRank
8920                ) {
8921         /* white pawn promotion */
8922         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8923         if (board[toY][toX] == EmptySquare) {
8924             board[toY][toX] = WhiteQueen;
8925         }
8926         if(gameInfo.variant==VariantBughouse ||
8927            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8928             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8929         board[fromY][fromX] = EmptySquare;
8930     } else if ((fromY == BOARD_HEIGHT-4)
8931                && (toX != fromX)
8932                && gameInfo.variant != VariantXiangqi
8933                && gameInfo.variant != VariantBerolina
8934                && (board[fromY][fromX] == WhitePawn)
8935                && (board[toY][toX] == EmptySquare)) {
8936         board[fromY][fromX] = EmptySquare;
8937         board[toY][toX] = WhitePawn;
8938         captured = board[toY - 1][toX];
8939         board[toY - 1][toX] = EmptySquare;
8940     } else if ((fromY == BOARD_HEIGHT-4)
8941                && (toX == fromX)
8942                && gameInfo.variant == VariantBerolina
8943                && (board[fromY][fromX] == WhitePawn)
8944                && (board[toY][toX] == EmptySquare)) {
8945         board[fromY][fromX] = EmptySquare;
8946         board[toY][toX] = WhitePawn;
8947         if(oldEP & EP_BEROLIN_A) {
8948                 captured = board[fromY][fromX-1];
8949                 board[fromY][fromX-1] = EmptySquare;
8950         }else{  captured = board[fromY][fromX+1];
8951                 board[fromY][fromX+1] = EmptySquare;
8952         }
8953     } else if (board[fromY][fromX] == king
8954         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8955                && toY == fromY && toX > fromX+1) {
8956         board[fromY][fromX] = EmptySquare;
8957         board[toY][toX] = king;
8958         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8959         board[fromY][BOARD_RGHT-1] = EmptySquare;
8960     } else if (board[fromY][fromX] == king
8961         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8962                && toY == fromY && toX < fromX-1) {
8963         board[fromY][fromX] = EmptySquare;
8964         board[toY][toX] = king;
8965         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8966         board[fromY][BOARD_LEFT] = EmptySquare;
8967     } else if (fromY == 7 && fromX == 3
8968                && board[fromY][fromX] == BlackKing
8969                && toY == 7 && toX == 5) {
8970         board[fromY][fromX] = EmptySquare;
8971         board[toY][toX] = BlackKing;
8972         board[fromY][7] = EmptySquare;
8973         board[toY][4] = BlackRook;
8974     } else if (fromY == 7 && fromX == 3
8975                && board[fromY][fromX] == BlackKing
8976                && toY == 7 && toX == 1) {
8977         board[fromY][fromX] = EmptySquare;
8978         board[toY][toX] = BlackKing;
8979         board[fromY][0] = EmptySquare;
8980         board[toY][2] = BlackRook;
8981     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8982                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8983                && toY < promoRank
8984                ) {
8985         /* black pawn promotion */
8986         board[toY][toX] = CharToPiece(ToLower(promoChar));
8987         if (board[toY][toX] == EmptySquare) {
8988             board[toY][toX] = BlackQueen;
8989         }
8990         if(gameInfo.variant==VariantBughouse ||
8991            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8992             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8993         board[fromY][fromX] = EmptySquare;
8994     } else if ((fromY == 3)
8995                && (toX != fromX)
8996                && gameInfo.variant != VariantXiangqi
8997                && gameInfo.variant != VariantBerolina
8998                && (board[fromY][fromX] == BlackPawn)
8999                && (board[toY][toX] == EmptySquare)) {
9000         board[fromY][fromX] = EmptySquare;
9001         board[toY][toX] = BlackPawn;
9002         captured = board[toY + 1][toX];
9003         board[toY + 1][toX] = EmptySquare;
9004     } else if ((fromY == 3)
9005                && (toX == fromX)
9006                && gameInfo.variant == VariantBerolina
9007                && (board[fromY][fromX] == BlackPawn)
9008                && (board[toY][toX] == EmptySquare)) {
9009         board[fromY][fromX] = EmptySquare;
9010         board[toY][toX] = BlackPawn;
9011         if(oldEP & EP_BEROLIN_A) {
9012                 captured = board[fromY][fromX-1];
9013                 board[fromY][fromX-1] = EmptySquare;
9014         }else{  captured = board[fromY][fromX+1];
9015                 board[fromY][fromX+1] = EmptySquare;
9016         }
9017     } else {
9018         board[toY][toX] = board[fromY][fromX];
9019         board[fromY][fromX] = EmptySquare;
9020     }
9021   }
9022
9023     if (gameInfo.holdingsWidth != 0) {
9024
9025       /* !!A lot more code needs to be written to support holdings  */
9026       /* [HGM] OK, so I have written it. Holdings are stored in the */
9027       /* penultimate board files, so they are automaticlly stored   */
9028       /* in the game history.                                       */
9029       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9030                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9031         /* Delete from holdings, by decreasing count */
9032         /* and erasing image if necessary            */
9033         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9034         if(p < (int) BlackPawn) { /* white drop */
9035              p -= (int)WhitePawn;
9036                  p = PieceToNumber((ChessSquare)p);
9037              if(p >= gameInfo.holdingsSize) p = 0;
9038              if(--board[p][BOARD_WIDTH-2] <= 0)
9039                   board[p][BOARD_WIDTH-1] = EmptySquare;
9040              if((int)board[p][BOARD_WIDTH-2] < 0)
9041                         board[p][BOARD_WIDTH-2] = 0;
9042         } else {                  /* black drop */
9043              p -= (int)BlackPawn;
9044                  p = PieceToNumber((ChessSquare)p);
9045              if(p >= gameInfo.holdingsSize) p = 0;
9046              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9047                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9048              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9049                         board[BOARD_HEIGHT-1-p][1] = 0;
9050         }
9051       }
9052       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9053           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9054         /* [HGM] holdings: Add to holdings, if holdings exist */
9055         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9056                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9057                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9058         }
9059         p = (int) captured;
9060         if (p >= (int) BlackPawn) {
9061           p -= (int)BlackPawn;
9062           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9063                   /* in Shogi restore piece to its original  first */
9064                   captured = (ChessSquare) (DEMOTED captured);
9065                   p = DEMOTED p;
9066           }
9067           p = PieceToNumber((ChessSquare)p);
9068           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9069           board[p][BOARD_WIDTH-2]++;
9070           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9071         } else {
9072           p -= (int)WhitePawn;
9073           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9074                   captured = (ChessSquare) (DEMOTED captured);
9075                   p = DEMOTED p;
9076           }
9077           p = PieceToNumber((ChessSquare)p);
9078           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9079           board[BOARD_HEIGHT-1-p][1]++;
9080           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9081         }
9082       }
9083     } else if (gameInfo.variant == VariantAtomic) {
9084       if (captured != EmptySquare) {
9085         int y, x;
9086         for (y = toY-1; y <= toY+1; y++) {
9087           for (x = toX-1; x <= toX+1; x++) {
9088             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9089                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9090               board[y][x] = EmptySquare;
9091             }
9092           }
9093         }
9094         board[toY][toX] = EmptySquare;
9095       }
9096     }
9097     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9098         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9099     } else
9100     if(promoChar == '+') {
9101         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9102         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9103     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9104         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9105     }
9106     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9107                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9108         // [HGM] superchess: take promotion piece out of holdings
9109         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9110         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9111             if(!--board[k][BOARD_WIDTH-2])
9112                 board[k][BOARD_WIDTH-1] = EmptySquare;
9113         } else {
9114             if(!--board[BOARD_HEIGHT-1-k][1])
9115                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9116         }
9117     }
9118
9119 }
9120
9121 /* Updates forwardMostMove */
9122 void
9123 MakeMove(fromX, fromY, toX, toY, promoChar)
9124      int fromX, fromY, toX, toY;
9125      int promoChar;
9126 {
9127 //    forwardMostMove++; // [HGM] bare: moved downstream
9128
9129     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9130         int timeLeft; static int lastLoadFlag=0; int king, piece;
9131         piece = boards[forwardMostMove][fromY][fromX];
9132         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9133         if(gameInfo.variant == VariantKnightmate)
9134             king += (int) WhiteUnicorn - (int) WhiteKing;
9135         if(forwardMostMove == 0) {
9136             if(blackPlaysFirst)
9137                 fprintf(serverMoves, "%s;", second.tidy);
9138             fprintf(serverMoves, "%s;", first.tidy);
9139             if(!blackPlaysFirst)
9140                 fprintf(serverMoves, "%s;", second.tidy);
9141         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9142         lastLoadFlag = loadFlag;
9143         // print base move
9144         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9145         // print castling suffix
9146         if( toY == fromY && piece == king ) {
9147             if(toX-fromX > 1)
9148                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9149             if(fromX-toX >1)
9150                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9151         }
9152         // e.p. suffix
9153         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9154              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9155              boards[forwardMostMove][toY][toX] == EmptySquare
9156              && fromX != toX && fromY != toY)
9157                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9158         // promotion suffix
9159         if(promoChar != NULLCHAR)
9160                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9161         if(!loadFlag) {
9162             fprintf(serverMoves, "/%d/%d",
9163                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9164             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9165             else                      timeLeft = blackTimeRemaining/1000;
9166             fprintf(serverMoves, "/%d", timeLeft);
9167         }
9168         fflush(serverMoves);
9169     }
9170
9171     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9172       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9173                         0, 1);
9174       return;
9175     }
9176     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9177     if (commentList[forwardMostMove+1] != NULL) {
9178         free(commentList[forwardMostMove+1]);
9179         commentList[forwardMostMove+1] = NULL;
9180     }
9181     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9182     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9183     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9184     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9185     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9186     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9187     gameInfo.result = GameUnfinished;
9188     if (gameInfo.resultDetails != NULL) {
9189         free(gameInfo.resultDetails);
9190         gameInfo.resultDetails = NULL;
9191     }
9192     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9193                               moveList[forwardMostMove - 1]);
9194     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9195                              PosFlags(forwardMostMove - 1),
9196                              fromY, fromX, toY, toX, promoChar,
9197                              parseList[forwardMostMove - 1]);
9198     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9199       case MT_NONE:
9200       case MT_STALEMATE:
9201       default:
9202         break;
9203       case MT_CHECK:
9204         if(gameInfo.variant != VariantShogi)
9205             strcat(parseList[forwardMostMove - 1], "+");
9206         break;
9207       case MT_CHECKMATE:
9208       case MT_STAINMATE:
9209         strcat(parseList[forwardMostMove - 1], "#");
9210         break;
9211     }
9212     if (appData.debugMode) {
9213         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9214     }
9215
9216 }
9217
9218 /* Updates currentMove if not pausing */
9219 void
9220 ShowMove(fromX, fromY, toX, toY)
9221 {
9222     int instant = (gameMode == PlayFromGameFile) ?
9223         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9224     if(appData.noGUI) return;
9225     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9226         if (!instant) {
9227             if (forwardMostMove == currentMove + 1) {
9228                 AnimateMove(boards[forwardMostMove - 1],
9229                             fromX, fromY, toX, toY);
9230             }
9231             if (appData.highlightLastMove) {
9232                 SetHighlights(fromX, fromY, toX, toY);
9233             }
9234         }
9235         currentMove = forwardMostMove;
9236     }
9237
9238     if (instant) return;
9239
9240     DisplayMove(currentMove - 1);
9241     DrawPosition(FALSE, boards[currentMove]);
9242     DisplayBothClocks();
9243     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9244 }
9245
9246 void SendEgtPath(ChessProgramState *cps)
9247 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9248         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9249
9250         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9251
9252         while(*p) {
9253             char c, *q = name+1, *r, *s;
9254
9255             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9256             while(*p && *p != ',') *q++ = *p++;
9257             *q++ = ':'; *q = 0;
9258             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9259                 strcmp(name, ",nalimov:") == 0 ) {
9260                 // take nalimov path from the menu-changeable option first, if it is defined
9261               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9262                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9263             } else
9264             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9265                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9266                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9267                 s = r = StrStr(s, ":") + 1; // beginning of path info
9268                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9269                 c = *r; *r = 0;             // temporarily null-terminate path info
9270                     *--q = 0;               // strip of trailig ':' from name
9271                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9272                 *r = c;
9273                 SendToProgram(buf,cps);     // send egtbpath command for this format
9274             }
9275             if(*p == ',') p++; // read away comma to position for next format name
9276         }
9277 }
9278
9279 void
9280 InitChessProgram(cps, setup)
9281      ChessProgramState *cps;
9282      int setup; /* [HGM] needed to setup FRC opening position */
9283 {
9284     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9285     if (appData.noChessProgram) return;
9286     hintRequested = FALSE;
9287     bookRequested = FALSE;
9288
9289     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9290     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9291     if(cps->memSize) { /* [HGM] memory */
9292       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9293         SendToProgram(buf, cps);
9294     }
9295     SendEgtPath(cps); /* [HGM] EGT */
9296     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9297       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9298         SendToProgram(buf, cps);
9299     }
9300
9301     SendToProgram(cps->initString, cps);
9302     if (gameInfo.variant != VariantNormal &&
9303         gameInfo.variant != VariantLoadable
9304         /* [HGM] also send variant if board size non-standard */
9305         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9306                                             ) {
9307       char *v = VariantName(gameInfo.variant);
9308       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9309         /* [HGM] in protocol 1 we have to assume all variants valid */
9310         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9311         DisplayFatalError(buf, 0, 1);
9312         return;
9313       }
9314
9315       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9316       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9317       if( gameInfo.variant == VariantXiangqi )
9318            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9319       if( gameInfo.variant == VariantShogi )
9320            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9321       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9322            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9323       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9324           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9325            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9326       if( gameInfo.variant == VariantCourier )
9327            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9328       if( gameInfo.variant == VariantSuper )
9329            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9330       if( gameInfo.variant == VariantGreat )
9331            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9332       if( gameInfo.variant == VariantSChess )
9333            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9334
9335       if(overruled) {
9336         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9337                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9338            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9339            if(StrStr(cps->variants, b) == NULL) {
9340                // specific sized variant not known, check if general sizing allowed
9341                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9342                    if(StrStr(cps->variants, "boardsize") == NULL) {
9343                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9344                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9345                        DisplayFatalError(buf, 0, 1);
9346                        return;
9347                    }
9348                    /* [HGM] here we really should compare with the maximum supported board size */
9349                }
9350            }
9351       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9352       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9353       SendToProgram(buf, cps);
9354     }
9355     currentlyInitializedVariant = gameInfo.variant;
9356
9357     /* [HGM] send opening position in FRC to first engine */
9358     if(setup) {
9359           SendToProgram("force\n", cps);
9360           SendBoard(cps, 0);
9361           /* engine is now in force mode! Set flag to wake it up after first move. */
9362           setboardSpoiledMachineBlack = 1;
9363     }
9364
9365     if (cps->sendICS) {
9366       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9367       SendToProgram(buf, cps);
9368     }
9369     cps->maybeThinking = FALSE;
9370     cps->offeredDraw = 0;
9371     if (!appData.icsActive) {
9372         SendTimeControl(cps, movesPerSession, timeControl,
9373                         timeIncrement, appData.searchDepth,
9374                         searchTime);
9375     }
9376     if (appData.showThinking
9377         // [HGM] thinking: four options require thinking output to be sent
9378         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9379                                 ) {
9380         SendToProgram("post\n", cps);
9381     }
9382     SendToProgram("hard\n", cps);
9383     if (!appData.ponderNextMove) {
9384         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9385            it without being sure what state we are in first.  "hard"
9386            is not a toggle, so that one is OK.
9387          */
9388         SendToProgram("easy\n", cps);
9389     }
9390     if (cps->usePing) {
9391       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9392       SendToProgram(buf, cps);
9393     }
9394     cps->initDone = TRUE;
9395 }
9396
9397
9398 void
9399 StartChessProgram(cps)
9400      ChessProgramState *cps;
9401 {
9402     char buf[MSG_SIZ];
9403     int err;
9404
9405     if (appData.noChessProgram) return;
9406     cps->initDone = FALSE;
9407
9408     if (strcmp(cps->host, "localhost") == 0) {
9409         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9410     } else if (*appData.remoteShell == NULLCHAR) {
9411         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9412     } else {
9413         if (*appData.remoteUser == NULLCHAR) {
9414           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9415                     cps->program);
9416         } else {
9417           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9418                     cps->host, appData.remoteUser, cps->program);
9419         }
9420         err = StartChildProcess(buf, "", &cps->pr);
9421     }
9422
9423     if (err != 0) {
9424       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9425         DisplayFatalError(buf, err, 1);
9426         cps->pr = NoProc;
9427         cps->isr = NULL;
9428         return;
9429     }
9430
9431     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9432     if (cps->protocolVersion > 1) {
9433       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9434       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9435       cps->comboCnt = 0;  //                and values of combo boxes
9436       SendToProgram(buf, cps);
9437     } else {
9438       SendToProgram("xboard\n", cps);
9439     }
9440 }
9441
9442 void
9443 TwoMachinesEventIfReady P((void))
9444 {
9445   static int curMess = 0;
9446   if (first.lastPing != first.lastPong) {
9447     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9448     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9449     return;
9450   }
9451   if (second.lastPing != second.lastPong) {
9452     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9453     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9454     return;
9455   }
9456   DisplayMessage("", ""); curMess = 0;
9457   ThawUI();
9458   TwoMachinesEvent();
9459 }
9460
9461 int
9462 CreateTourney(char *name)
9463 {
9464         FILE *f;
9465         if(name[0] == NULLCHAR) return 0;
9466         f = fopen(appData.tourneyFile, "r");
9467         if(f) { // file exists
9468             ParseArgsFromFile(f); // parse it
9469         } else {
9470             f = fopen(appData.tourneyFile, "w");
9471             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9472                 // create a file with tournament description
9473                 fprintf(f, "-participants {%s}\n", appData.participants);
9474                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9475                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9476                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9477                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9478                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9479                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9480                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9481                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9482                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9483                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9484                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9485                 if(searchTime > 0)
9486                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9487                 else {
9488                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9489                         fprintf(f, "-tc %s\n", appData.timeControl);
9490                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9491                 }
9492                 fprintf(f, "-results \"\"\n");
9493             }
9494         }
9495         fclose(f);
9496         appData.noChessProgram = FALSE;
9497         appData.clockMode = TRUE;
9498         SetGNUMode();
9499         return 1;
9500 }
9501
9502 #define MAXENGINES 1000
9503 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9504
9505 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9506 {
9507     char buf[MSG_SIZ], *p, *q;
9508     int i=1;
9509     while(*names) {
9510         p = names; q = buf;
9511         while(*p && *p != '\n') *q++ = *p++;
9512         *q = 0;
9513         if(engineList[i]) free(engineList[i]);
9514         engineList[i] = strdup(buf);
9515         if(*p == '\n') p++;
9516         TidyProgramName(engineList[i], "localhost", buf);
9517         if(engineMnemonic[i]) free(engineMnemonic[i]);
9518         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9519             strcat(buf, " (");
9520             sscanf(q + 8, "%s", buf + strlen(buf));
9521             strcat(buf, ")");
9522         }
9523         engineMnemonic[i] = strdup(buf);
9524         names = p; i++;
9525       if(i > MAXENGINES - 2) break;
9526     }
9527     engineList[i] = NULL;
9528 }
9529
9530 // following implemented as macro to avoid type limitations
9531 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9532
9533 void SwapEngines(int n)
9534 {   // swap settings for first engine and other engine (so far only some selected options)
9535     int h;
9536     char *p;
9537     if(n == 0) return;
9538     SWAP(directory, p)
9539     SWAP(chessProgram, p)
9540     SWAP(isUCI, h)
9541     SWAP(hasOwnBookUCI, h)
9542     SWAP(protocolVersion, h)
9543     SWAP(reuse, h)
9544     SWAP(scoreIsAbsolute, h)
9545     SWAP(timeOdds, h)
9546     SWAP(logo, p)
9547 }
9548
9549 void
9550 SetPlayer(int player)
9551 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9552     int i;
9553     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9554     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9555                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9556     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9557     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9558     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9559     if(mnemonic[i]) {
9560         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9561         ParseArgsFromString(resetOptions);
9562         ParseArgsFromString(buf);
9563     }
9564     free(engineName);
9565 }
9566
9567 int
9568 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9569 {   // determine players from game number
9570     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9571
9572     if(appData.tourneyType == 0) {
9573         roundsPerCycle = (nPlayers - 1) | 1;
9574         pairingsPerRound = nPlayers / 2;
9575     } else if(appData.tourneyType > 0) {
9576         roundsPerCycle = nPlayers - appData.tourneyType;
9577         pairingsPerRound = appData.tourneyType;
9578     }
9579     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9580     gamesPerCycle = gamesPerRound * roundsPerCycle;
9581     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9582     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9583     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9584     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9585     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9586     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9587
9588     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9589     if(appData.roundSync) *syncInterval = gamesPerRound;
9590
9591     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9592
9593     if(appData.tourneyType == 0) {
9594         if(curPairing == (nPlayers-1)/2 ) {
9595             *whitePlayer = curRound;
9596             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9597         } else {
9598             *whitePlayer = curRound - pairingsPerRound + curPairing;
9599             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9600             *blackPlayer = curRound + pairingsPerRound - curPairing;
9601             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9602         }
9603     } else if(appData.tourneyType > 0) {
9604         *whitePlayer = curPairing;
9605         *blackPlayer = curRound + appData.tourneyType;
9606     }
9607
9608     // take care of white/black alternation per round. 
9609     // For cycles and games this is already taken care of by default, derived from matchGame!
9610     return curRound & 1;
9611 }
9612
9613 int
9614 NextTourneyGame(int nr, int *swapColors)
9615 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9616     char *p, *q;
9617     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9618     FILE *tf;
9619     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9620     tf = fopen(appData.tourneyFile, "r");
9621     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9622     ParseArgsFromFile(tf); fclose(tf);
9623     InitTimeControls(); // TC might be altered from tourney file
9624
9625     p = appData.participants;
9626     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9627     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9628
9629     if(syncInterval) {
9630         p = q = appData.results;
9631         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9632         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9633             DisplayMessage(_("Waiting for other game(s)"),"");
9634             waitingForGame = TRUE;
9635             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9636             return 0;
9637         }
9638         waitingForGame = FALSE;
9639     }
9640
9641     if(first.pr != NoProc) return 1; // engines already loaded
9642
9643     // redefine engines, engine dir, etc.
9644     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9645     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9646     SwapEngines(1);
9647     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9648     SwapEngines(1);         // and make that valid for second engine by swapping
9649     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9650     InitEngine(&second, 1);
9651     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9652     return 1;
9653 }
9654
9655 void
9656 NextMatchGame()
9657 {   // performs game initialization that does not invoke engines, and then tries to start the game
9658     int firstWhite, swapColors = 0;
9659     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9660     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9661     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9662     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9663     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9664     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9665     Reset(FALSE, first.pr != NoProc);
9666     appData.noChessProgram = FALSE;
9667     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9668     TwoMachinesEvent();
9669 }
9670
9671 void UserAdjudicationEvent( int result )
9672 {
9673     ChessMove gameResult = GameIsDrawn;
9674
9675     if( result > 0 ) {
9676         gameResult = WhiteWins;
9677     }
9678     else if( result < 0 ) {
9679         gameResult = BlackWins;
9680     }
9681
9682     if( gameMode == TwoMachinesPlay ) {
9683         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9684     }
9685 }
9686
9687
9688 // [HGM] save: calculate checksum of game to make games easily identifiable
9689 int StringCheckSum(char *s)
9690 {
9691         int i = 0;
9692         if(s==NULL) return 0;
9693         while(*s) i = i*259 + *s++;
9694         return i;
9695 }
9696
9697 int GameCheckSum()
9698 {
9699         int i, sum=0;
9700         for(i=backwardMostMove; i<forwardMostMove; i++) {
9701                 sum += pvInfoList[i].depth;
9702                 sum += StringCheckSum(parseList[i]);
9703                 sum += StringCheckSum(commentList[i]);
9704                 sum *= 261;
9705         }
9706         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9707         return sum + StringCheckSum(commentList[i]);
9708 } // end of save patch
9709
9710 void
9711 GameEnds(result, resultDetails, whosays)
9712      ChessMove result;
9713      char *resultDetails;
9714      int whosays;
9715 {
9716     GameMode nextGameMode;
9717     int isIcsGame;
9718     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9719
9720     if(endingGame) return; /* [HGM] crash: forbid recursion */
9721     endingGame = 1;
9722     if(twoBoards) { // [HGM] dual: switch back to one board
9723         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9724         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9725     }
9726     if (appData.debugMode) {
9727       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9728               result, resultDetails ? resultDetails : "(null)", whosays);
9729     }
9730
9731     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9732
9733     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9734         /* If we are playing on ICS, the server decides when the
9735            game is over, but the engine can offer to draw, claim
9736            a draw, or resign.
9737          */
9738 #if ZIPPY
9739         if (appData.zippyPlay && first.initDone) {
9740             if (result == GameIsDrawn) {
9741                 /* In case draw still needs to be claimed */
9742                 SendToICS(ics_prefix);
9743                 SendToICS("draw\n");
9744             } else if (StrCaseStr(resultDetails, "resign")) {
9745                 SendToICS(ics_prefix);
9746                 SendToICS("resign\n");
9747             }
9748         }
9749 #endif
9750         endingGame = 0; /* [HGM] crash */
9751         return;
9752     }
9753
9754     /* If we're loading the game from a file, stop */
9755     if (whosays == GE_FILE) {
9756       (void) StopLoadGameTimer();
9757       gameFileFP = NULL;
9758     }
9759
9760     /* Cancel draw offers */
9761     first.offeredDraw = second.offeredDraw = 0;
9762
9763     /* If this is an ICS game, only ICS can really say it's done;
9764        if not, anyone can. */
9765     isIcsGame = (gameMode == IcsPlayingWhite ||
9766                  gameMode == IcsPlayingBlack ||
9767                  gameMode == IcsObserving    ||
9768                  gameMode == IcsExamining);
9769
9770     if (!isIcsGame || whosays == GE_ICS) {
9771         /* OK -- not an ICS game, or ICS said it was done */
9772         StopClocks();
9773         if (!isIcsGame && !appData.noChessProgram)
9774           SetUserThinkingEnables();
9775
9776         /* [HGM] if a machine claims the game end we verify this claim */
9777         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9778             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9779                 char claimer;
9780                 ChessMove trueResult = (ChessMove) -1;
9781
9782                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9783                                             first.twoMachinesColor[0] :
9784                                             second.twoMachinesColor[0] ;
9785
9786                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9787                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9788                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9789                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9790                 } else
9791                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9792                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9793                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9794                 } else
9795                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9796                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9797                 }
9798
9799                 // now verify win claims, but not in drop games, as we don't understand those yet
9800                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9801                                                  || gameInfo.variant == VariantGreat) &&
9802                     (result == WhiteWins && claimer == 'w' ||
9803                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9804                       if (appData.debugMode) {
9805                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9806                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9807                       }
9808                       if(result != trueResult) {
9809                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9810                               result = claimer == 'w' ? BlackWins : WhiteWins;
9811                               resultDetails = buf;
9812                       }
9813                 } else
9814                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9815                     && (forwardMostMove <= backwardMostMove ||
9816                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9817                         (claimer=='b')==(forwardMostMove&1))
9818                                                                                   ) {
9819                       /* [HGM] verify: draws that were not flagged are false claims */
9820                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9821                       result = claimer == 'w' ? BlackWins : WhiteWins;
9822                       resultDetails = buf;
9823                 }
9824                 /* (Claiming a loss is accepted no questions asked!) */
9825             }
9826             /* [HGM] bare: don't allow bare King to win */
9827             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9828                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9829                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9830                && result != GameIsDrawn)
9831             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9832                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9833                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9834                         if(p >= 0 && p <= (int)WhiteKing) k++;
9835                 }
9836                 if (appData.debugMode) {
9837                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9838                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9839                 }
9840                 if(k <= 1) {
9841                         result = GameIsDrawn;
9842                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9843                         resultDetails = buf;
9844                 }
9845             }
9846         }
9847
9848
9849         if(serverMoves != NULL && !loadFlag) { char c = '=';
9850             if(result==WhiteWins) c = '+';
9851             if(result==BlackWins) c = '-';
9852             if(resultDetails != NULL)
9853                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9854         }
9855         if (resultDetails != NULL) {
9856             gameInfo.result = result;
9857             gameInfo.resultDetails = StrSave(resultDetails);
9858
9859             /* display last move only if game was not loaded from file */
9860             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9861                 DisplayMove(currentMove - 1);
9862
9863             if (forwardMostMove != 0) {
9864                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9865                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9866                                                                 ) {
9867                     if (*appData.saveGameFile != NULLCHAR) {
9868                         SaveGameToFile(appData.saveGameFile, TRUE);
9869                     } else if (appData.autoSaveGames) {
9870                         AutoSaveGame();
9871                     }
9872                     if (*appData.savePositionFile != NULLCHAR) {
9873                         SavePositionToFile(appData.savePositionFile);
9874                     }
9875                 }
9876             }
9877
9878             /* Tell program how game ended in case it is learning */
9879             /* [HGM] Moved this to after saving the PGN, just in case */
9880             /* engine died and we got here through time loss. In that */
9881             /* case we will get a fatal error writing the pipe, which */
9882             /* would otherwise lose us the PGN.                       */
9883             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9884             /* output during GameEnds should never be fatal anymore   */
9885             if (gameMode == MachinePlaysWhite ||
9886                 gameMode == MachinePlaysBlack ||
9887                 gameMode == TwoMachinesPlay ||
9888                 gameMode == IcsPlayingWhite ||
9889                 gameMode == IcsPlayingBlack ||
9890                 gameMode == BeginningOfGame) {
9891                 char buf[MSG_SIZ];
9892                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9893                         resultDetails);
9894                 if (first.pr != NoProc) {
9895                     SendToProgram(buf, &first);
9896                 }
9897                 if (second.pr != NoProc &&
9898                     gameMode == TwoMachinesPlay) {
9899                     SendToProgram(buf, &second);
9900                 }
9901             }
9902         }
9903
9904         if (appData.icsActive) {
9905             if (appData.quietPlay &&
9906                 (gameMode == IcsPlayingWhite ||
9907                  gameMode == IcsPlayingBlack)) {
9908                 SendToICS(ics_prefix);
9909                 SendToICS("set shout 1\n");
9910             }
9911             nextGameMode = IcsIdle;
9912             ics_user_moved = FALSE;
9913             /* clean up premove.  It's ugly when the game has ended and the
9914              * premove highlights are still on the board.
9915              */
9916             if (gotPremove) {
9917               gotPremove = FALSE;
9918               ClearPremoveHighlights();
9919               DrawPosition(FALSE, boards[currentMove]);
9920             }
9921             if (whosays == GE_ICS) {
9922                 switch (result) {
9923                 case WhiteWins:
9924                     if (gameMode == IcsPlayingWhite)
9925                         PlayIcsWinSound();
9926                     else if(gameMode == IcsPlayingBlack)
9927                         PlayIcsLossSound();
9928                     break;
9929                 case BlackWins:
9930                     if (gameMode == IcsPlayingBlack)
9931                         PlayIcsWinSound();
9932                     else if(gameMode == IcsPlayingWhite)
9933                         PlayIcsLossSound();
9934                     break;
9935                 case GameIsDrawn:
9936                     PlayIcsDrawSound();
9937                     break;
9938                 default:
9939                     PlayIcsUnfinishedSound();
9940                 }
9941             }
9942         } else if (gameMode == EditGame ||
9943                    gameMode == PlayFromGameFile ||
9944                    gameMode == AnalyzeMode ||
9945                    gameMode == AnalyzeFile) {
9946             nextGameMode = gameMode;
9947         } else {
9948             nextGameMode = EndOfGame;
9949         }
9950         pausing = FALSE;
9951         ModeHighlight();
9952     } else {
9953         nextGameMode = gameMode;
9954     }
9955
9956     if (appData.noChessProgram) {
9957         gameMode = nextGameMode;
9958         ModeHighlight();
9959         endingGame = 0; /* [HGM] crash */
9960         return;
9961     }
9962
9963     if (first.reuse) {
9964         /* Put first chess program into idle state */
9965         if (first.pr != NoProc &&
9966             (gameMode == MachinePlaysWhite ||
9967              gameMode == MachinePlaysBlack ||
9968              gameMode == TwoMachinesPlay ||
9969              gameMode == IcsPlayingWhite ||
9970              gameMode == IcsPlayingBlack ||
9971              gameMode == BeginningOfGame)) {
9972             SendToProgram("force\n", &first);
9973             if (first.usePing) {
9974               char buf[MSG_SIZ];
9975               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9976               SendToProgram(buf, &first);
9977             }
9978         }
9979     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9980         /* Kill off first chess program */
9981         if (first.isr != NULL)
9982           RemoveInputSource(first.isr);
9983         first.isr = NULL;
9984
9985         if (first.pr != NoProc) {
9986             ExitAnalyzeMode();
9987             DoSleep( appData.delayBeforeQuit );
9988             SendToProgram("quit\n", &first);
9989             DoSleep( appData.delayAfterQuit );
9990             DestroyChildProcess(first.pr, first.useSigterm);
9991         }
9992         first.pr = NoProc;
9993     }
9994     if (second.reuse) {
9995         /* Put second chess program into idle state */
9996         if (second.pr != NoProc &&
9997             gameMode == TwoMachinesPlay) {
9998             SendToProgram("force\n", &second);
9999             if (second.usePing) {
10000               char buf[MSG_SIZ];
10001               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10002               SendToProgram(buf, &second);
10003             }
10004         }
10005     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10006         /* Kill off second chess program */
10007         if (second.isr != NULL)
10008           RemoveInputSource(second.isr);
10009         second.isr = NULL;
10010
10011         if (second.pr != NoProc) {
10012             DoSleep( appData.delayBeforeQuit );
10013             SendToProgram("quit\n", &second);
10014             DoSleep( appData.delayAfterQuit );
10015             DestroyChildProcess(second.pr, second.useSigterm);
10016         }
10017         second.pr = NoProc;
10018     }
10019
10020     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10021         char resChar = '=';
10022         switch (result) {
10023         case WhiteWins:
10024           resChar = '+';
10025           if (first.twoMachinesColor[0] == 'w') {
10026             first.matchWins++;
10027           } else {
10028             second.matchWins++;
10029           }
10030           break;
10031         case BlackWins:
10032           resChar = '-';
10033           if (first.twoMachinesColor[0] == 'b') {
10034             first.matchWins++;
10035           } else {
10036             second.matchWins++;
10037           }
10038           break;
10039         case GameUnfinished:
10040           resChar = ' ';
10041         default:
10042           break;
10043         }
10044
10045         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10046         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10047             ReserveGame(nextGame, resChar); // sets nextGame
10048             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10049         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10050
10051         if (nextGame <= appData.matchGames && !abortMatch) {
10052             gameMode = nextGameMode;
10053             matchGame = nextGame; // this will be overruled in tourney mode!
10054             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10055             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10056             endingGame = 0; /* [HGM] crash */
10057             return;
10058         } else {
10059             gameMode = nextGameMode;
10060             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10061                      first.tidy, second.tidy,
10062                      first.matchWins, second.matchWins,
10063                      appData.matchGames - (first.matchWins + second.matchWins));
10064             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10065             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10066                 first.twoMachinesColor = "black\n";
10067                 second.twoMachinesColor = "white\n";
10068             } else {
10069                 first.twoMachinesColor = "white\n";
10070                 second.twoMachinesColor = "black\n";
10071             }
10072         }
10073     }
10074     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10075         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10076       ExitAnalyzeMode();
10077     gameMode = nextGameMode;
10078     ModeHighlight();
10079     endingGame = 0;  /* [HGM] crash */
10080     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10081         if(matchMode == TRUE) { // match through command line: exit with or without popup
10082             if(ranking) {
10083                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10084                 else ExitEvent(0);
10085             } else DisplayFatalError(buf, 0, 0);
10086         } else { // match through menu; just stop, with or without popup
10087             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10088             if(ranking){
10089                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10090             } else DisplayNote(buf);
10091       }
10092       if(ranking) free(ranking);
10093     }
10094 }
10095
10096 /* Assumes program was just initialized (initString sent).
10097    Leaves program in force mode. */
10098 void
10099 FeedMovesToProgram(cps, upto)
10100      ChessProgramState *cps;
10101      int upto;
10102 {
10103     int i;
10104
10105     if (appData.debugMode)
10106       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10107               startedFromSetupPosition ? "position and " : "",
10108               backwardMostMove, upto, cps->which);
10109     if(currentlyInitializedVariant != gameInfo.variant) {
10110       char buf[MSG_SIZ];
10111         // [HGM] variantswitch: make engine aware of new variant
10112         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10113                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10114         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10115         SendToProgram(buf, cps);
10116         currentlyInitializedVariant = gameInfo.variant;
10117     }
10118     SendToProgram("force\n", cps);
10119     if (startedFromSetupPosition) {
10120         SendBoard(cps, backwardMostMove);
10121     if (appData.debugMode) {
10122         fprintf(debugFP, "feedMoves\n");
10123     }
10124     }
10125     for (i = backwardMostMove; i < upto; i++) {
10126         SendMoveToProgram(i, cps);
10127     }
10128 }
10129
10130
10131 int
10132 ResurrectChessProgram()
10133 {
10134      /* The chess program may have exited.
10135         If so, restart it and feed it all the moves made so far. */
10136     static int doInit = 0;
10137
10138     if (appData.noChessProgram) return 1;
10139
10140     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10141         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10142         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10143         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10144     } else {
10145         if (first.pr != NoProc) return 1;
10146         StartChessProgram(&first);
10147     }
10148     InitChessProgram(&first, FALSE);
10149     FeedMovesToProgram(&first, currentMove);
10150
10151     if (!first.sendTime) {
10152         /* can't tell gnuchess what its clock should read,
10153            so we bow to its notion. */
10154         ResetClocks();
10155         timeRemaining[0][currentMove] = whiteTimeRemaining;
10156         timeRemaining[1][currentMove] = blackTimeRemaining;
10157     }
10158
10159     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10160                 appData.icsEngineAnalyze) && first.analysisSupport) {
10161       SendToProgram("analyze\n", &first);
10162       first.analyzing = TRUE;
10163     }
10164     return 1;
10165 }
10166
10167 /*
10168  * Button procedures
10169  */
10170 void
10171 Reset(redraw, init)
10172      int redraw, init;
10173 {
10174     int i;
10175
10176     if (appData.debugMode) {
10177         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10178                 redraw, init, gameMode);
10179     }
10180     CleanupTail(); // [HGM] vari: delete any stored variations
10181     pausing = pauseExamInvalid = FALSE;
10182     startedFromSetupPosition = blackPlaysFirst = FALSE;
10183     firstMove = TRUE;
10184     whiteFlag = blackFlag = FALSE;
10185     userOfferedDraw = FALSE;
10186     hintRequested = bookRequested = FALSE;
10187     first.maybeThinking = FALSE;
10188     second.maybeThinking = FALSE;
10189     first.bookSuspend = FALSE; // [HGM] book
10190     second.bookSuspend = FALSE;
10191     thinkOutput[0] = NULLCHAR;
10192     lastHint[0] = NULLCHAR;
10193     ClearGameInfo(&gameInfo);
10194     gameInfo.variant = StringToVariant(appData.variant);
10195     ics_user_moved = ics_clock_paused = FALSE;
10196     ics_getting_history = H_FALSE;
10197     ics_gamenum = -1;
10198     white_holding[0] = black_holding[0] = NULLCHAR;
10199     ClearProgramStats();
10200     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10201
10202     ResetFrontEnd();
10203     ClearHighlights();
10204     flipView = appData.flipView;
10205     ClearPremoveHighlights();
10206     gotPremove = FALSE;
10207     alarmSounded = FALSE;
10208
10209     GameEnds(EndOfFile, NULL, GE_PLAYER);
10210     if(appData.serverMovesName != NULL) {
10211         /* [HGM] prepare to make moves file for broadcasting */
10212         clock_t t = clock();
10213         if(serverMoves != NULL) fclose(serverMoves);
10214         serverMoves = fopen(appData.serverMovesName, "r");
10215         if(serverMoves != NULL) {
10216             fclose(serverMoves);
10217             /* delay 15 sec before overwriting, so all clients can see end */
10218             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10219         }
10220         serverMoves = fopen(appData.serverMovesName, "w");
10221     }
10222
10223     ExitAnalyzeMode();
10224     gameMode = BeginningOfGame;
10225     ModeHighlight();
10226     if(appData.icsActive) gameInfo.variant = VariantNormal;
10227     currentMove = forwardMostMove = backwardMostMove = 0;
10228     InitPosition(redraw);
10229     for (i = 0; i < MAX_MOVES; i++) {
10230         if (commentList[i] != NULL) {
10231             free(commentList[i]);
10232             commentList[i] = NULL;
10233         }
10234     }
10235     ResetClocks();
10236     timeRemaining[0][0] = whiteTimeRemaining;
10237     timeRemaining[1][0] = blackTimeRemaining;
10238
10239     if (first.pr == NULL) {
10240         StartChessProgram(&first);
10241     }
10242     if (init) {
10243             InitChessProgram(&first, startedFromSetupPosition);
10244     }
10245     DisplayTitle("");
10246     DisplayMessage("", "");
10247     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10248     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10249 }
10250
10251 void
10252 AutoPlayGameLoop()
10253 {
10254     for (;;) {
10255         if (!AutoPlayOneMove())
10256           return;
10257         if (matchMode || appData.timeDelay == 0)
10258           continue;
10259         if (appData.timeDelay < 0)
10260           return;
10261         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10262         break;
10263     }
10264 }
10265
10266
10267 int
10268 AutoPlayOneMove()
10269 {
10270     int fromX, fromY, toX, toY;
10271
10272     if (appData.debugMode) {
10273       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10274     }
10275
10276     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10277       return FALSE;
10278
10279     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10280       pvInfoList[currentMove].depth = programStats.depth;
10281       pvInfoList[currentMove].score = programStats.score;
10282       pvInfoList[currentMove].time  = 0;
10283       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10284     }
10285
10286     if (currentMove >= forwardMostMove) {
10287       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10288       gameMode = EditGame;
10289       ModeHighlight();
10290
10291       /* [AS] Clear current move marker at the end of a game */
10292       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10293
10294       return FALSE;
10295     }
10296
10297     toX = moveList[currentMove][2] - AAA;
10298     toY = moveList[currentMove][3] - ONE;
10299
10300     if (moveList[currentMove][1] == '@') {
10301         if (appData.highlightLastMove) {
10302             SetHighlights(-1, -1, toX, toY);
10303         }
10304     } else {
10305         fromX = moveList[currentMove][0] - AAA;
10306         fromY = moveList[currentMove][1] - ONE;
10307
10308         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10309
10310         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10311
10312         if (appData.highlightLastMove) {
10313             SetHighlights(fromX, fromY, toX, toY);
10314         }
10315     }
10316     DisplayMove(currentMove);
10317     SendMoveToProgram(currentMove++, &first);
10318     DisplayBothClocks();
10319     DrawPosition(FALSE, boards[currentMove]);
10320     // [HGM] PV info: always display, routine tests if empty
10321     DisplayComment(currentMove - 1, commentList[currentMove]);
10322     return TRUE;
10323 }
10324
10325
10326 int
10327 LoadGameOneMove(readAhead)
10328      ChessMove readAhead;
10329 {
10330     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10331     char promoChar = NULLCHAR;
10332     ChessMove moveType;
10333     char move[MSG_SIZ];
10334     char *p, *q;
10335
10336     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10337         gameMode != AnalyzeMode && gameMode != Training) {
10338         gameFileFP = NULL;
10339         return FALSE;
10340     }
10341
10342     yyboardindex = forwardMostMove;
10343     if (readAhead != EndOfFile) {
10344       moveType = readAhead;
10345     } else {
10346       if (gameFileFP == NULL)
10347           return FALSE;
10348       moveType = (ChessMove) Myylex();
10349     }
10350
10351     done = FALSE;
10352     switch (moveType) {
10353       case Comment:
10354         if (appData.debugMode)
10355           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10356         p = yy_text;
10357
10358         /* append the comment but don't display it */
10359         AppendComment(currentMove, p, FALSE);
10360         return TRUE;
10361
10362       case WhiteCapturesEnPassant:
10363       case BlackCapturesEnPassant:
10364       case WhitePromotion:
10365       case BlackPromotion:
10366       case WhiteNonPromotion:
10367       case BlackNonPromotion:
10368       case NormalMove:
10369       case WhiteKingSideCastle:
10370       case WhiteQueenSideCastle:
10371       case BlackKingSideCastle:
10372       case BlackQueenSideCastle:
10373       case WhiteKingSideCastleWild:
10374       case WhiteQueenSideCastleWild:
10375       case BlackKingSideCastleWild:
10376       case BlackQueenSideCastleWild:
10377       /* PUSH Fabien */
10378       case WhiteHSideCastleFR:
10379       case WhiteASideCastleFR:
10380       case BlackHSideCastleFR:
10381       case BlackASideCastleFR:
10382       /* POP Fabien */
10383         if (appData.debugMode)
10384           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10385         fromX = currentMoveString[0] - AAA;
10386         fromY = currentMoveString[1] - ONE;
10387         toX = currentMoveString[2] - AAA;
10388         toY = currentMoveString[3] - ONE;
10389         promoChar = currentMoveString[4];
10390         break;
10391
10392       case WhiteDrop:
10393       case BlackDrop:
10394         if (appData.debugMode)
10395           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10396         fromX = moveType == WhiteDrop ?
10397           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10398         (int) CharToPiece(ToLower(currentMoveString[0]));
10399         fromY = DROP_RANK;
10400         toX = currentMoveString[2] - AAA;
10401         toY = currentMoveString[3] - ONE;
10402         break;
10403
10404       case WhiteWins:
10405       case BlackWins:
10406       case GameIsDrawn:
10407       case GameUnfinished:
10408         if (appData.debugMode)
10409           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10410         p = strchr(yy_text, '{');
10411         if (p == NULL) p = strchr(yy_text, '(');
10412         if (p == NULL) {
10413             p = yy_text;
10414             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10415         } else {
10416             q = strchr(p, *p == '{' ? '}' : ')');
10417             if (q != NULL) *q = NULLCHAR;
10418             p++;
10419         }
10420         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10421         GameEnds(moveType, p, GE_FILE);
10422         done = TRUE;
10423         if (cmailMsgLoaded) {
10424             ClearHighlights();
10425             flipView = WhiteOnMove(currentMove);
10426             if (moveType == GameUnfinished) flipView = !flipView;
10427             if (appData.debugMode)
10428               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10429         }
10430         break;
10431
10432       case EndOfFile:
10433         if (appData.debugMode)
10434           fprintf(debugFP, "Parser hit end of file\n");
10435         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10436           case MT_NONE:
10437           case MT_CHECK:
10438             break;
10439           case MT_CHECKMATE:
10440           case MT_STAINMATE:
10441             if (WhiteOnMove(currentMove)) {
10442                 GameEnds(BlackWins, "Black mates", GE_FILE);
10443             } else {
10444                 GameEnds(WhiteWins, "White mates", GE_FILE);
10445             }
10446             break;
10447           case MT_STALEMATE:
10448             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10449             break;
10450         }
10451         done = TRUE;
10452         break;
10453
10454       case MoveNumberOne:
10455         if (lastLoadGameStart == GNUChessGame) {
10456             /* GNUChessGames have numbers, but they aren't move numbers */
10457             if (appData.debugMode)
10458               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10459                       yy_text, (int) moveType);
10460             return LoadGameOneMove(EndOfFile); /* tail recursion */
10461         }
10462         /* else fall thru */
10463
10464       case XBoardGame:
10465       case GNUChessGame:
10466       case PGNTag:
10467         /* Reached start of next game in file */
10468         if (appData.debugMode)
10469           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10470         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10471           case MT_NONE:
10472           case MT_CHECK:
10473             break;
10474           case MT_CHECKMATE:
10475           case MT_STAINMATE:
10476             if (WhiteOnMove(currentMove)) {
10477                 GameEnds(BlackWins, "Black mates", GE_FILE);
10478             } else {
10479                 GameEnds(WhiteWins, "White mates", GE_FILE);
10480             }
10481             break;
10482           case MT_STALEMATE:
10483             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10484             break;
10485         }
10486         done = TRUE;
10487         break;
10488
10489       case PositionDiagram:     /* should not happen; ignore */
10490       case ElapsedTime:         /* ignore */
10491       case NAG:                 /* ignore */
10492         if (appData.debugMode)
10493           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10494                   yy_text, (int) moveType);
10495         return LoadGameOneMove(EndOfFile); /* tail recursion */
10496
10497       case IllegalMove:
10498         if (appData.testLegality) {
10499             if (appData.debugMode)
10500               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10501             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10502                     (forwardMostMove / 2) + 1,
10503                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10504             DisplayError(move, 0);
10505             done = TRUE;
10506         } else {
10507             if (appData.debugMode)
10508               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10509                       yy_text, currentMoveString);
10510             fromX = currentMoveString[0] - AAA;
10511             fromY = currentMoveString[1] - ONE;
10512             toX = currentMoveString[2] - AAA;
10513             toY = currentMoveString[3] - ONE;
10514             promoChar = currentMoveString[4];
10515         }
10516         break;
10517
10518       case AmbiguousMove:
10519         if (appData.debugMode)
10520           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10521         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10522                 (forwardMostMove / 2) + 1,
10523                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10524         DisplayError(move, 0);
10525         done = TRUE;
10526         break;
10527
10528       default:
10529       case ImpossibleMove:
10530         if (appData.debugMode)
10531           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10532         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10533                 (forwardMostMove / 2) + 1,
10534                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10535         DisplayError(move, 0);
10536         done = TRUE;
10537         break;
10538     }
10539
10540     if (done) {
10541         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10542             DrawPosition(FALSE, boards[currentMove]);
10543             DisplayBothClocks();
10544             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10545               DisplayComment(currentMove - 1, commentList[currentMove]);
10546         }
10547         (void) StopLoadGameTimer();
10548         gameFileFP = NULL;
10549         cmailOldMove = forwardMostMove;
10550         return FALSE;
10551     } else {
10552         /* currentMoveString is set as a side-effect of yylex */
10553
10554         thinkOutput[0] = NULLCHAR;
10555         MakeMove(fromX, fromY, toX, toY, promoChar);
10556         currentMove = forwardMostMove;
10557         return TRUE;
10558     }
10559 }
10560
10561 /* Load the nth game from the given file */
10562 int
10563 LoadGameFromFile(filename, n, title, useList)
10564      char *filename;
10565      int n;
10566      char *title;
10567      /*Boolean*/ int useList;
10568 {
10569     FILE *f;
10570     char buf[MSG_SIZ];
10571
10572     if (strcmp(filename, "-") == 0) {
10573         f = stdin;
10574         title = "stdin";
10575     } else {
10576         f = fopen(filename, "rb");
10577         if (f == NULL) {
10578           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10579             DisplayError(buf, errno);
10580             return FALSE;
10581         }
10582     }
10583     if (fseek(f, 0, 0) == -1) {
10584         /* f is not seekable; probably a pipe */
10585         useList = FALSE;
10586     }
10587     if (useList && n == 0) {
10588         int error = GameListBuild(f);
10589         if (error) {
10590             DisplayError(_("Cannot build game list"), error);
10591         } else if (!ListEmpty(&gameList) &&
10592                    ((ListGame *) gameList.tailPred)->number > 1) {
10593             GameListPopUp(f, title);
10594             return TRUE;
10595         }
10596         GameListDestroy();
10597         n = 1;
10598     }
10599     if (n == 0) n = 1;
10600     return LoadGame(f, n, title, FALSE);
10601 }
10602
10603
10604 void
10605 MakeRegisteredMove()
10606 {
10607     int fromX, fromY, toX, toY;
10608     char promoChar;
10609     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10610         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10611           case CMAIL_MOVE:
10612           case CMAIL_DRAW:
10613             if (appData.debugMode)
10614               fprintf(debugFP, "Restoring %s for game %d\n",
10615                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10616
10617             thinkOutput[0] = NULLCHAR;
10618             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10619             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10620             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10621             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10622             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10623             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10624             MakeMove(fromX, fromY, toX, toY, promoChar);
10625             ShowMove(fromX, fromY, toX, toY);
10626
10627             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10628               case MT_NONE:
10629               case MT_CHECK:
10630                 break;
10631
10632               case MT_CHECKMATE:
10633               case MT_STAINMATE:
10634                 if (WhiteOnMove(currentMove)) {
10635                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10636                 } else {
10637                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10638                 }
10639                 break;
10640
10641               case MT_STALEMATE:
10642                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10643                 break;
10644             }
10645
10646             break;
10647
10648           case CMAIL_RESIGN:
10649             if (WhiteOnMove(currentMove)) {
10650                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10651             } else {
10652                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10653             }
10654             break;
10655
10656           case CMAIL_ACCEPT:
10657             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10658             break;
10659
10660           default:
10661             break;
10662         }
10663     }
10664
10665     return;
10666 }
10667
10668 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10669 int
10670 CmailLoadGame(f, gameNumber, title, useList)
10671      FILE *f;
10672      int gameNumber;
10673      char *title;
10674      int useList;
10675 {
10676     int retVal;
10677
10678     if (gameNumber > nCmailGames) {
10679         DisplayError(_("No more games in this message"), 0);
10680         return FALSE;
10681     }
10682     if (f == lastLoadGameFP) {
10683         int offset = gameNumber - lastLoadGameNumber;
10684         if (offset == 0) {
10685             cmailMsg[0] = NULLCHAR;
10686             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10687                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10688                 nCmailMovesRegistered--;
10689             }
10690             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10691             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10692                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10693             }
10694         } else {
10695             if (! RegisterMove()) return FALSE;
10696         }
10697     }
10698
10699     retVal = LoadGame(f, gameNumber, title, useList);
10700
10701     /* Make move registered during previous look at this game, if any */
10702     MakeRegisteredMove();
10703
10704     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10705         commentList[currentMove]
10706           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10707         DisplayComment(currentMove - 1, commentList[currentMove]);
10708     }
10709
10710     return retVal;
10711 }
10712
10713 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10714 int
10715 ReloadGame(offset)
10716      int offset;
10717 {
10718     int gameNumber = lastLoadGameNumber + offset;
10719     if (lastLoadGameFP == NULL) {
10720         DisplayError(_("No game has been loaded yet"), 0);
10721         return FALSE;
10722     }
10723     if (gameNumber <= 0) {
10724         DisplayError(_("Can't back up any further"), 0);
10725         return FALSE;
10726     }
10727     if (cmailMsgLoaded) {
10728         return CmailLoadGame(lastLoadGameFP, gameNumber,
10729                              lastLoadGameTitle, lastLoadGameUseList);
10730     } else {
10731         return LoadGame(lastLoadGameFP, gameNumber,
10732                         lastLoadGameTitle, lastLoadGameUseList);
10733     }
10734 }
10735
10736
10737
10738 /* Load the nth game from open file f */
10739 int
10740 LoadGame(f, gameNumber, title, useList)
10741      FILE *f;
10742      int gameNumber;
10743      char *title;
10744      int useList;
10745 {
10746     ChessMove cm;
10747     char buf[MSG_SIZ];
10748     int gn = gameNumber;
10749     ListGame *lg = NULL;
10750     int numPGNTags = 0;
10751     int err;
10752     GameMode oldGameMode;
10753     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10754
10755     if (appData.debugMode)
10756         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10757
10758     if (gameMode == Training )
10759         SetTrainingModeOff();
10760
10761     oldGameMode = gameMode;
10762     if (gameMode != BeginningOfGame) {
10763       Reset(FALSE, TRUE);
10764     }
10765
10766     gameFileFP = f;
10767     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10768         fclose(lastLoadGameFP);
10769     }
10770
10771     if (useList) {
10772         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10773
10774         if (lg) {
10775             fseek(f, lg->offset, 0);
10776             GameListHighlight(gameNumber);
10777             gn = 1;
10778         }
10779         else {
10780             DisplayError(_("Game number out of range"), 0);
10781             return FALSE;
10782         }
10783     } else {
10784         GameListDestroy();
10785         if (fseek(f, 0, 0) == -1) {
10786             if (f == lastLoadGameFP ?
10787                 gameNumber == lastLoadGameNumber + 1 :
10788                 gameNumber == 1) {
10789                 gn = 1;
10790             } else {
10791                 DisplayError(_("Can't seek on game file"), 0);
10792                 return FALSE;
10793             }
10794         }
10795     }
10796     lastLoadGameFP = f;
10797     lastLoadGameNumber = gameNumber;
10798     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10799     lastLoadGameUseList = useList;
10800
10801     yynewfile(f);
10802
10803     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10804       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10805                 lg->gameInfo.black);
10806             DisplayTitle(buf);
10807     } else if (*title != NULLCHAR) {
10808         if (gameNumber > 1) {
10809           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10810             DisplayTitle(buf);
10811         } else {
10812             DisplayTitle(title);
10813         }
10814     }
10815
10816     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10817         gameMode = PlayFromGameFile;
10818         ModeHighlight();
10819     }
10820
10821     currentMove = forwardMostMove = backwardMostMove = 0;
10822     CopyBoard(boards[0], initialPosition);
10823     StopClocks();
10824
10825     /*
10826      * Skip the first gn-1 games in the file.
10827      * Also skip over anything that precedes an identifiable
10828      * start of game marker, to avoid being confused by
10829      * garbage at the start of the file.  Currently
10830      * recognized start of game markers are the move number "1",
10831      * the pattern "gnuchess .* game", the pattern
10832      * "^[#;%] [^ ]* game file", and a PGN tag block.
10833      * A game that starts with one of the latter two patterns
10834      * will also have a move number 1, possibly
10835      * following a position diagram.
10836      * 5-4-02: Let's try being more lenient and allowing a game to
10837      * start with an unnumbered move.  Does that break anything?
10838      */
10839     cm = lastLoadGameStart = EndOfFile;
10840     while (gn > 0) {
10841         yyboardindex = forwardMostMove;
10842         cm = (ChessMove) Myylex();
10843         switch (cm) {
10844           case EndOfFile:
10845             if (cmailMsgLoaded) {
10846                 nCmailGames = CMAIL_MAX_GAMES - gn;
10847             } else {
10848                 Reset(TRUE, TRUE);
10849                 DisplayError(_("Game not found in file"), 0);
10850             }
10851             return FALSE;
10852
10853           case GNUChessGame:
10854           case XBoardGame:
10855             gn--;
10856             lastLoadGameStart = cm;
10857             break;
10858
10859           case MoveNumberOne:
10860             switch (lastLoadGameStart) {
10861               case GNUChessGame:
10862               case XBoardGame:
10863               case PGNTag:
10864                 break;
10865               case MoveNumberOne:
10866               case EndOfFile:
10867                 gn--;           /* count this game */
10868                 lastLoadGameStart = cm;
10869                 break;
10870               default:
10871                 /* impossible */
10872                 break;
10873             }
10874             break;
10875
10876           case PGNTag:
10877             switch (lastLoadGameStart) {
10878               case GNUChessGame:
10879               case PGNTag:
10880               case MoveNumberOne:
10881               case EndOfFile:
10882                 gn--;           /* count this game */
10883                 lastLoadGameStart = cm;
10884                 break;
10885               case XBoardGame:
10886                 lastLoadGameStart = cm; /* game counted already */
10887                 break;
10888               default:
10889                 /* impossible */
10890                 break;
10891             }
10892             if (gn > 0) {
10893                 do {
10894                     yyboardindex = forwardMostMove;
10895                     cm = (ChessMove) Myylex();
10896                 } while (cm == PGNTag || cm == Comment);
10897             }
10898             break;
10899
10900           case WhiteWins:
10901           case BlackWins:
10902           case GameIsDrawn:
10903             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10904                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10905                     != CMAIL_OLD_RESULT) {
10906                     nCmailResults ++ ;
10907                     cmailResult[  CMAIL_MAX_GAMES
10908                                 - gn - 1] = CMAIL_OLD_RESULT;
10909                 }
10910             }
10911             break;
10912
10913           case NormalMove:
10914             /* Only a NormalMove can be at the start of a game
10915              * without a position diagram. */
10916             if (lastLoadGameStart == EndOfFile ) {
10917               gn--;
10918               lastLoadGameStart = MoveNumberOne;
10919             }
10920             break;
10921
10922           default:
10923             break;
10924         }
10925     }
10926
10927     if (appData.debugMode)
10928       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10929
10930     if (cm == XBoardGame) {
10931         /* Skip any header junk before position diagram and/or move 1 */
10932         for (;;) {
10933             yyboardindex = forwardMostMove;
10934             cm = (ChessMove) Myylex();
10935
10936             if (cm == EndOfFile ||
10937                 cm == GNUChessGame || cm == XBoardGame) {
10938                 /* Empty game; pretend end-of-file and handle later */
10939                 cm = EndOfFile;
10940                 break;
10941             }
10942
10943             if (cm == MoveNumberOne || cm == PositionDiagram ||
10944                 cm == PGNTag || cm == Comment)
10945               break;
10946         }
10947     } else if (cm == GNUChessGame) {
10948         if (gameInfo.event != NULL) {
10949             free(gameInfo.event);
10950         }
10951         gameInfo.event = StrSave(yy_text);
10952     }
10953
10954     startedFromSetupPosition = FALSE;
10955     while (cm == PGNTag) {
10956         if (appData.debugMode)
10957           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10958         err = ParsePGNTag(yy_text, &gameInfo);
10959         if (!err) numPGNTags++;
10960
10961         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10962         if(gameInfo.variant != oldVariant) {
10963             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10964             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10965             InitPosition(TRUE);
10966             oldVariant = gameInfo.variant;
10967             if (appData.debugMode)
10968               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10969         }
10970
10971
10972         if (gameInfo.fen != NULL) {
10973           Board initial_position;
10974           startedFromSetupPosition = TRUE;
10975           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10976             Reset(TRUE, TRUE);
10977             DisplayError(_("Bad FEN position in file"), 0);
10978             return FALSE;
10979           }
10980           CopyBoard(boards[0], initial_position);
10981           if (blackPlaysFirst) {
10982             currentMove = forwardMostMove = backwardMostMove = 1;
10983             CopyBoard(boards[1], initial_position);
10984             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10985             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10986             timeRemaining[0][1] = whiteTimeRemaining;
10987             timeRemaining[1][1] = blackTimeRemaining;
10988             if (commentList[0] != NULL) {
10989               commentList[1] = commentList[0];
10990               commentList[0] = NULL;
10991             }
10992           } else {
10993             currentMove = forwardMostMove = backwardMostMove = 0;
10994           }
10995           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10996           {   int i;
10997               initialRulePlies = FENrulePlies;
10998               for( i=0; i< nrCastlingRights; i++ )
10999                   initialRights[i] = initial_position[CASTLING][i];
11000           }
11001           yyboardindex = forwardMostMove;
11002           free(gameInfo.fen);
11003           gameInfo.fen = NULL;
11004         }
11005
11006         yyboardindex = forwardMostMove;
11007         cm = (ChessMove) Myylex();
11008
11009         /* Handle comments interspersed among the tags */
11010         while (cm == Comment) {
11011             char *p;
11012             if (appData.debugMode)
11013               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11014             p = yy_text;
11015             AppendComment(currentMove, p, FALSE);
11016             yyboardindex = forwardMostMove;
11017             cm = (ChessMove) Myylex();
11018         }
11019     }
11020
11021     /* don't rely on existence of Event tag since if game was
11022      * pasted from clipboard the Event tag may not exist
11023      */
11024     if (numPGNTags > 0){
11025         char *tags;
11026         if (gameInfo.variant == VariantNormal) {
11027           VariantClass v = StringToVariant(gameInfo.event);
11028           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11029           if(v < VariantShogi) gameInfo.variant = v;
11030         }
11031         if (!matchMode) {
11032           if( appData.autoDisplayTags ) {
11033             tags = PGNTags(&gameInfo);
11034             TagsPopUp(tags, CmailMsg());
11035             free(tags);
11036           }
11037         }
11038     } else {
11039         /* Make something up, but don't display it now */
11040         SetGameInfo();
11041         TagsPopDown();
11042     }
11043
11044     if (cm == PositionDiagram) {
11045         int i, j;
11046         char *p;
11047         Board initial_position;
11048
11049         if (appData.debugMode)
11050           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11051
11052         if (!startedFromSetupPosition) {
11053             p = yy_text;
11054             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11055               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11056                 switch (*p) {
11057                   case '{':
11058                   case '[':
11059                   case '-':
11060                   case ' ':
11061                   case '\t':
11062                   case '\n':
11063                   case '\r':
11064                     break;
11065                   default:
11066                     initial_position[i][j++] = CharToPiece(*p);
11067                     break;
11068                 }
11069             while (*p == ' ' || *p == '\t' ||
11070                    *p == '\n' || *p == '\r') p++;
11071
11072             if (strncmp(p, "black", strlen("black"))==0)
11073               blackPlaysFirst = TRUE;
11074             else
11075               blackPlaysFirst = FALSE;
11076             startedFromSetupPosition = TRUE;
11077
11078             CopyBoard(boards[0], initial_position);
11079             if (blackPlaysFirst) {
11080                 currentMove = forwardMostMove = backwardMostMove = 1;
11081                 CopyBoard(boards[1], initial_position);
11082                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11083                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11084                 timeRemaining[0][1] = whiteTimeRemaining;
11085                 timeRemaining[1][1] = blackTimeRemaining;
11086                 if (commentList[0] != NULL) {
11087                     commentList[1] = commentList[0];
11088                     commentList[0] = NULL;
11089                 }
11090             } else {
11091                 currentMove = forwardMostMove = backwardMostMove = 0;
11092             }
11093         }
11094         yyboardindex = forwardMostMove;
11095         cm = (ChessMove) Myylex();
11096     }
11097
11098     if (first.pr == NoProc) {
11099         StartChessProgram(&first);
11100     }
11101     InitChessProgram(&first, FALSE);
11102     SendToProgram("force\n", &first);
11103     if (startedFromSetupPosition) {
11104         SendBoard(&first, forwardMostMove);
11105     if (appData.debugMode) {
11106         fprintf(debugFP, "Load Game\n");
11107     }
11108         DisplayBothClocks();
11109     }
11110
11111     /* [HGM] server: flag to write setup moves in broadcast file as one */
11112     loadFlag = appData.suppressLoadMoves;
11113
11114     while (cm == Comment) {
11115         char *p;
11116         if (appData.debugMode)
11117           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11118         p = yy_text;
11119         AppendComment(currentMove, p, FALSE);
11120         yyboardindex = forwardMostMove;
11121         cm = (ChessMove) Myylex();
11122     }
11123
11124     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11125         cm == WhiteWins || cm == BlackWins ||
11126         cm == GameIsDrawn || cm == GameUnfinished) {
11127         DisplayMessage("", _("No moves in game"));
11128         if (cmailMsgLoaded) {
11129             if (appData.debugMode)
11130               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11131             ClearHighlights();
11132             flipView = FALSE;
11133         }
11134         DrawPosition(FALSE, boards[currentMove]);
11135         DisplayBothClocks();
11136         gameMode = EditGame;
11137         ModeHighlight();
11138         gameFileFP = NULL;
11139         cmailOldMove = 0;
11140         return TRUE;
11141     }
11142
11143     // [HGM] PV info: routine tests if comment empty
11144     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11145         DisplayComment(currentMove - 1, commentList[currentMove]);
11146     }
11147     if (!matchMode && appData.timeDelay != 0)
11148       DrawPosition(FALSE, boards[currentMove]);
11149
11150     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11151       programStats.ok_to_send = 1;
11152     }
11153
11154     /* if the first token after the PGN tags is a move
11155      * and not move number 1, retrieve it from the parser
11156      */
11157     if (cm != MoveNumberOne)
11158         LoadGameOneMove(cm);
11159
11160     /* load the remaining moves from the file */
11161     while (LoadGameOneMove(EndOfFile)) {
11162       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11163       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11164     }
11165
11166     /* rewind to the start of the game */
11167     currentMove = backwardMostMove;
11168
11169     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11170
11171     if (oldGameMode == AnalyzeFile ||
11172         oldGameMode == AnalyzeMode) {
11173       AnalyzeFileEvent();
11174     }
11175
11176     if (matchMode || appData.timeDelay == 0) {
11177       ToEndEvent();
11178       gameMode = EditGame;
11179       ModeHighlight();
11180     } else if (appData.timeDelay > 0) {
11181       AutoPlayGameLoop();
11182     }
11183
11184     if (appData.debugMode)
11185         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11186
11187     loadFlag = 0; /* [HGM] true game starts */
11188     return TRUE;
11189 }
11190
11191 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11192 int
11193 ReloadPosition(offset)
11194      int offset;
11195 {
11196     int positionNumber = lastLoadPositionNumber + offset;
11197     if (lastLoadPositionFP == NULL) {
11198         DisplayError(_("No position has been loaded yet"), 0);
11199         return FALSE;
11200     }
11201     if (positionNumber <= 0) {
11202         DisplayError(_("Can't back up any further"), 0);
11203         return FALSE;
11204     }
11205     return LoadPosition(lastLoadPositionFP, positionNumber,
11206                         lastLoadPositionTitle);
11207 }
11208
11209 /* Load the nth position from the given file */
11210 int
11211 LoadPositionFromFile(filename, n, title)
11212      char *filename;
11213      int n;
11214      char *title;
11215 {
11216     FILE *f;
11217     char buf[MSG_SIZ];
11218
11219     if (strcmp(filename, "-") == 0) {
11220         return LoadPosition(stdin, n, "stdin");
11221     } else {
11222         f = fopen(filename, "rb");
11223         if (f == NULL) {
11224             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11225             DisplayError(buf, errno);
11226             return FALSE;
11227         } else {
11228             return LoadPosition(f, n, title);
11229         }
11230     }
11231 }
11232
11233 /* Load the nth position from the given open file, and close it */
11234 int
11235 LoadPosition(f, positionNumber, title)
11236      FILE *f;
11237      int positionNumber;
11238      char *title;
11239 {
11240     char *p, line[MSG_SIZ];
11241     Board initial_position;
11242     int i, j, fenMode, pn;
11243
11244     if (gameMode == Training )
11245         SetTrainingModeOff();
11246
11247     if (gameMode != BeginningOfGame) {
11248         Reset(FALSE, TRUE);
11249     }
11250     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11251         fclose(lastLoadPositionFP);
11252     }
11253     if (positionNumber == 0) positionNumber = 1;
11254     lastLoadPositionFP = f;
11255     lastLoadPositionNumber = positionNumber;
11256     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11257     if (first.pr == NoProc) {
11258       StartChessProgram(&first);
11259       InitChessProgram(&first, FALSE);
11260     }
11261     pn = positionNumber;
11262     if (positionNumber < 0) {
11263         /* Negative position number means to seek to that byte offset */
11264         if (fseek(f, -positionNumber, 0) == -1) {
11265             DisplayError(_("Can't seek on position file"), 0);
11266             return FALSE;
11267         };
11268         pn = 1;
11269     } else {
11270         if (fseek(f, 0, 0) == -1) {
11271             if (f == lastLoadPositionFP ?
11272                 positionNumber == lastLoadPositionNumber + 1 :
11273                 positionNumber == 1) {
11274                 pn = 1;
11275             } else {
11276                 DisplayError(_("Can't seek on position file"), 0);
11277                 return FALSE;
11278             }
11279         }
11280     }
11281     /* See if this file is FEN or old-style xboard */
11282     if (fgets(line, MSG_SIZ, f) == NULL) {
11283         DisplayError(_("Position not found in file"), 0);
11284         return FALSE;
11285     }
11286     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11287     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11288
11289     if (pn >= 2) {
11290         if (fenMode || line[0] == '#') pn--;
11291         while (pn > 0) {
11292             /* skip positions before number pn */
11293             if (fgets(line, MSG_SIZ, f) == NULL) {
11294                 Reset(TRUE, TRUE);
11295                 DisplayError(_("Position not found in file"), 0);
11296                 return FALSE;
11297             }
11298             if (fenMode || line[0] == '#') pn--;
11299         }
11300     }
11301
11302     if (fenMode) {
11303         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11304             DisplayError(_("Bad FEN position in file"), 0);
11305             return FALSE;
11306         }
11307     } else {
11308         (void) fgets(line, MSG_SIZ, f);
11309         (void) fgets(line, MSG_SIZ, f);
11310
11311         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11312             (void) fgets(line, MSG_SIZ, f);
11313             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11314                 if (*p == ' ')
11315                   continue;
11316                 initial_position[i][j++] = CharToPiece(*p);
11317             }
11318         }
11319
11320         blackPlaysFirst = FALSE;
11321         if (!feof(f)) {
11322             (void) fgets(line, MSG_SIZ, f);
11323             if (strncmp(line, "black", strlen("black"))==0)
11324               blackPlaysFirst = TRUE;
11325         }
11326     }
11327     startedFromSetupPosition = TRUE;
11328
11329     SendToProgram("force\n", &first);
11330     CopyBoard(boards[0], initial_position);
11331     if (blackPlaysFirst) {
11332         currentMove = forwardMostMove = backwardMostMove = 1;
11333         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11334         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11335         CopyBoard(boards[1], initial_position);
11336         DisplayMessage("", _("Black to play"));
11337     } else {
11338         currentMove = forwardMostMove = backwardMostMove = 0;
11339         DisplayMessage("", _("White to play"));
11340     }
11341     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11342     SendBoard(&first, forwardMostMove);
11343     if (appData.debugMode) {
11344 int i, j;
11345   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11346   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11347         fprintf(debugFP, "Load Position\n");
11348     }
11349
11350     if (positionNumber > 1) {
11351       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11352         DisplayTitle(line);
11353     } else {
11354         DisplayTitle(title);
11355     }
11356     gameMode = EditGame;
11357     ModeHighlight();
11358     ResetClocks();
11359     timeRemaining[0][1] = whiteTimeRemaining;
11360     timeRemaining[1][1] = blackTimeRemaining;
11361     DrawPosition(FALSE, boards[currentMove]);
11362
11363     return TRUE;
11364 }
11365
11366
11367 void
11368 CopyPlayerNameIntoFileName(dest, src)
11369      char **dest, *src;
11370 {
11371     while (*src != NULLCHAR && *src != ',') {
11372         if (*src == ' ') {
11373             *(*dest)++ = '_';
11374             src++;
11375         } else {
11376             *(*dest)++ = *src++;
11377         }
11378     }
11379 }
11380
11381 char *DefaultFileName(ext)
11382      char *ext;
11383 {
11384     static char def[MSG_SIZ];
11385     char *p;
11386
11387     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11388         p = def;
11389         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11390         *p++ = '-';
11391         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11392         *p++ = '.';
11393         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11394     } else {
11395         def[0] = NULLCHAR;
11396     }
11397     return def;
11398 }
11399
11400 /* Save the current game to the given file */
11401 int
11402 SaveGameToFile(filename, append)
11403      char *filename;
11404      int append;
11405 {
11406     FILE *f;
11407     char buf[MSG_SIZ];
11408     int result;
11409
11410     if (strcmp(filename, "-") == 0) {
11411         return SaveGame(stdout, 0, NULL);
11412     } else {
11413         f = fopen(filename, append ? "a" : "w");
11414         if (f == NULL) {
11415             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11416             DisplayError(buf, errno);
11417             return FALSE;
11418         } else {
11419             safeStrCpy(buf, lastMsg, MSG_SIZ);
11420             DisplayMessage(_("Waiting for access to save file"), "");
11421             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11422             DisplayMessage(_("Saving game"), "");
11423             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11424             result = SaveGame(f, 0, NULL);
11425             DisplayMessage(buf, "");
11426             return result;
11427         }
11428     }
11429 }
11430
11431 char *
11432 SavePart(str)
11433      char *str;
11434 {
11435     static char buf[MSG_SIZ];
11436     char *p;
11437
11438     p = strchr(str, ' ');
11439     if (p == NULL) return str;
11440     strncpy(buf, str, p - str);
11441     buf[p - str] = NULLCHAR;
11442     return buf;
11443 }
11444
11445 #define PGN_MAX_LINE 75
11446
11447 #define PGN_SIDE_WHITE  0
11448 #define PGN_SIDE_BLACK  1
11449
11450 /* [AS] */
11451 static int FindFirstMoveOutOfBook( int side )
11452 {
11453     int result = -1;
11454
11455     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11456         int index = backwardMostMove;
11457         int has_book_hit = 0;
11458
11459         if( (index % 2) != side ) {
11460             index++;
11461         }
11462
11463         while( index < forwardMostMove ) {
11464             /* Check to see if engine is in book */
11465             int depth = pvInfoList[index].depth;
11466             int score = pvInfoList[index].score;
11467             int in_book = 0;
11468
11469             if( depth <= 2 ) {
11470                 in_book = 1;
11471             }
11472             else if( score == 0 && depth == 63 ) {
11473                 in_book = 1; /* Zappa */
11474             }
11475             else if( score == 2 && depth == 99 ) {
11476                 in_book = 1; /* Abrok */
11477             }
11478
11479             has_book_hit += in_book;
11480
11481             if( ! in_book ) {
11482                 result = index;
11483
11484                 break;
11485             }
11486
11487             index += 2;
11488         }
11489     }
11490
11491     return result;
11492 }
11493
11494 /* [AS] */
11495 void GetOutOfBookInfo( char * buf )
11496 {
11497     int oob[2];
11498     int i;
11499     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11500
11501     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11502     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11503
11504     *buf = '\0';
11505
11506     if( oob[0] >= 0 || oob[1] >= 0 ) {
11507         for( i=0; i<2; i++ ) {
11508             int idx = oob[i];
11509
11510             if( idx >= 0 ) {
11511                 if( i > 0 && oob[0] >= 0 ) {
11512                     strcat( buf, "   " );
11513                 }
11514
11515                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11516                 sprintf( buf+strlen(buf), "%s%.2f",
11517                     pvInfoList[idx].score >= 0 ? "+" : "",
11518                     pvInfoList[idx].score / 100.0 );
11519             }
11520         }
11521     }
11522 }
11523
11524 /* Save game in PGN style and close the file */
11525 int
11526 SaveGamePGN(f)
11527      FILE *f;
11528 {
11529     int i, offset, linelen, newblock;
11530     time_t tm;
11531 //    char *movetext;
11532     char numtext[32];
11533     int movelen, numlen, blank;
11534     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11535
11536     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11537
11538     tm = time((time_t *) NULL);
11539
11540     PrintPGNTags(f, &gameInfo);
11541
11542     if (backwardMostMove > 0 || startedFromSetupPosition) {
11543         char *fen = PositionToFEN(backwardMostMove, NULL);
11544         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11545         fprintf(f, "\n{--------------\n");
11546         PrintPosition(f, backwardMostMove);
11547         fprintf(f, "--------------}\n");
11548         free(fen);
11549     }
11550     else {
11551         /* [AS] Out of book annotation */
11552         if( appData.saveOutOfBookInfo ) {
11553             char buf[64];
11554
11555             GetOutOfBookInfo( buf );
11556
11557             if( buf[0] != '\0' ) {
11558                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11559             }
11560         }
11561
11562         fprintf(f, "\n");
11563     }
11564
11565     i = backwardMostMove;
11566     linelen = 0;
11567     newblock = TRUE;
11568
11569     while (i < forwardMostMove) {
11570         /* Print comments preceding this move */
11571         if (commentList[i] != NULL) {
11572             if (linelen > 0) fprintf(f, "\n");
11573             fprintf(f, "%s", commentList[i]);
11574             linelen = 0;
11575             newblock = TRUE;
11576         }
11577
11578         /* Format move number */
11579         if ((i % 2) == 0)
11580           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11581         else
11582           if (newblock)
11583             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11584           else
11585             numtext[0] = NULLCHAR;
11586
11587         numlen = strlen(numtext);
11588         newblock = FALSE;
11589
11590         /* Print move number */
11591         blank = linelen > 0 && numlen > 0;
11592         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11593             fprintf(f, "\n");
11594             linelen = 0;
11595             blank = 0;
11596         }
11597         if (blank) {
11598             fprintf(f, " ");
11599             linelen++;
11600         }
11601         fprintf(f, "%s", numtext);
11602         linelen += numlen;
11603
11604         /* Get move */
11605         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11606         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11607
11608         /* Print move */
11609         blank = linelen > 0 && movelen > 0;
11610         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11611             fprintf(f, "\n");
11612             linelen = 0;
11613             blank = 0;
11614         }
11615         if (blank) {
11616             fprintf(f, " ");
11617             linelen++;
11618         }
11619         fprintf(f, "%s", move_buffer);
11620         linelen += movelen;
11621
11622         /* [AS] Add PV info if present */
11623         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11624             /* [HGM] add time */
11625             char buf[MSG_SIZ]; int seconds;
11626
11627             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11628
11629             if( seconds <= 0)
11630               buf[0] = 0;
11631             else
11632               if( seconds < 30 )
11633                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11634               else
11635                 {
11636                   seconds = (seconds + 4)/10; // round to full seconds
11637                   if( seconds < 60 )
11638                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11639                   else
11640                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11641                 }
11642
11643             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11644                       pvInfoList[i].score >= 0 ? "+" : "",
11645                       pvInfoList[i].score / 100.0,
11646                       pvInfoList[i].depth,
11647                       buf );
11648
11649             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11650
11651             /* Print score/depth */
11652             blank = linelen > 0 && movelen > 0;
11653             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11654                 fprintf(f, "\n");
11655                 linelen = 0;
11656                 blank = 0;
11657             }
11658             if (blank) {
11659                 fprintf(f, " ");
11660                 linelen++;
11661             }
11662             fprintf(f, "%s", move_buffer);
11663             linelen += movelen;
11664         }
11665
11666         i++;
11667     }
11668
11669     /* Start a new line */
11670     if (linelen > 0) fprintf(f, "\n");
11671
11672     /* Print comments after last move */
11673     if (commentList[i] != NULL) {
11674         fprintf(f, "%s\n", commentList[i]);
11675     }
11676
11677     /* Print result */
11678     if (gameInfo.resultDetails != NULL &&
11679         gameInfo.resultDetails[0] != NULLCHAR) {
11680         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11681                 PGNResult(gameInfo.result));
11682     } else {
11683         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11684     }
11685
11686     fclose(f);
11687     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11688     return TRUE;
11689 }
11690
11691 /* Save game in old style and close the file */
11692 int
11693 SaveGameOldStyle(f)
11694      FILE *f;
11695 {
11696     int i, offset;
11697     time_t tm;
11698
11699     tm = time((time_t *) NULL);
11700
11701     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11702     PrintOpponents(f);
11703
11704     if (backwardMostMove > 0 || startedFromSetupPosition) {
11705         fprintf(f, "\n[--------------\n");
11706         PrintPosition(f, backwardMostMove);
11707         fprintf(f, "--------------]\n");
11708     } else {
11709         fprintf(f, "\n");
11710     }
11711
11712     i = backwardMostMove;
11713     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11714
11715     while (i < forwardMostMove) {
11716         if (commentList[i] != NULL) {
11717             fprintf(f, "[%s]\n", commentList[i]);
11718         }
11719
11720         if ((i % 2) == 1) {
11721             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11722             i++;
11723         } else {
11724             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11725             i++;
11726             if (commentList[i] != NULL) {
11727                 fprintf(f, "\n");
11728                 continue;
11729             }
11730             if (i >= forwardMostMove) {
11731                 fprintf(f, "\n");
11732                 break;
11733             }
11734             fprintf(f, "%s\n", parseList[i]);
11735             i++;
11736         }
11737     }
11738
11739     if (commentList[i] != NULL) {
11740         fprintf(f, "[%s]\n", commentList[i]);
11741     }
11742
11743     /* This isn't really the old style, but it's close enough */
11744     if (gameInfo.resultDetails != NULL &&
11745         gameInfo.resultDetails[0] != NULLCHAR) {
11746         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11747                 gameInfo.resultDetails);
11748     } else {
11749         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11750     }
11751
11752     fclose(f);
11753     return TRUE;
11754 }
11755
11756 /* Save the current game to open file f and close the file */
11757 int
11758 SaveGame(f, dummy, dummy2)
11759      FILE *f;
11760      int dummy;
11761      char *dummy2;
11762 {
11763     if (gameMode == EditPosition) EditPositionDone(TRUE);
11764     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11765     if (appData.oldSaveStyle)
11766       return SaveGameOldStyle(f);
11767     else
11768       return SaveGamePGN(f);
11769 }
11770
11771 /* Save the current position to the given file */
11772 int
11773 SavePositionToFile(filename)
11774      char *filename;
11775 {
11776     FILE *f;
11777     char buf[MSG_SIZ];
11778
11779     if (strcmp(filename, "-") == 0) {
11780         return SavePosition(stdout, 0, NULL);
11781     } else {
11782         f = fopen(filename, "a");
11783         if (f == NULL) {
11784             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11785             DisplayError(buf, errno);
11786             return FALSE;
11787         } else {
11788             safeStrCpy(buf, lastMsg, MSG_SIZ);
11789             DisplayMessage(_("Waiting for access to save file"), "");
11790             flock(fileno(f), LOCK_EX); // [HGM] lock
11791             DisplayMessage(_("Saving position"), "");
11792             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11793             SavePosition(f, 0, NULL);
11794             DisplayMessage(buf, "");
11795             return TRUE;
11796         }
11797     }
11798 }
11799
11800 /* Save the current position to the given open file and close the file */
11801 int
11802 SavePosition(f, dummy, dummy2)
11803      FILE *f;
11804      int dummy;
11805      char *dummy2;
11806 {
11807     time_t tm;
11808     char *fen;
11809
11810     if (gameMode == EditPosition) EditPositionDone(TRUE);
11811     if (appData.oldSaveStyle) {
11812         tm = time((time_t *) NULL);
11813
11814         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11815         PrintOpponents(f);
11816         fprintf(f, "[--------------\n");
11817         PrintPosition(f, currentMove);
11818         fprintf(f, "--------------]\n");
11819     } else {
11820         fen = PositionToFEN(currentMove, NULL);
11821         fprintf(f, "%s\n", fen);
11822         free(fen);
11823     }
11824     fclose(f);
11825     return TRUE;
11826 }
11827
11828 void
11829 ReloadCmailMsgEvent(unregister)
11830      int unregister;
11831 {
11832 #if !WIN32
11833     static char *inFilename = NULL;
11834     static char *outFilename;
11835     int i;
11836     struct stat inbuf, outbuf;
11837     int status;
11838
11839     /* Any registered moves are unregistered if unregister is set, */
11840     /* i.e. invoked by the signal handler */
11841     if (unregister) {
11842         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11843             cmailMoveRegistered[i] = FALSE;
11844             if (cmailCommentList[i] != NULL) {
11845                 free(cmailCommentList[i]);
11846                 cmailCommentList[i] = NULL;
11847             }
11848         }
11849         nCmailMovesRegistered = 0;
11850     }
11851
11852     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11853         cmailResult[i] = CMAIL_NOT_RESULT;
11854     }
11855     nCmailResults = 0;
11856
11857     if (inFilename == NULL) {
11858         /* Because the filenames are static they only get malloced once  */
11859         /* and they never get freed                                      */
11860         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11861         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11862
11863         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11864         sprintf(outFilename, "%s.out", appData.cmailGameName);
11865     }
11866
11867     status = stat(outFilename, &outbuf);
11868     if (status < 0) {
11869         cmailMailedMove = FALSE;
11870     } else {
11871         status = stat(inFilename, &inbuf);
11872         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11873     }
11874
11875     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11876        counts the games, notes how each one terminated, etc.
11877
11878        It would be nice to remove this kludge and instead gather all
11879        the information while building the game list.  (And to keep it
11880        in the game list nodes instead of having a bunch of fixed-size
11881        parallel arrays.)  Note this will require getting each game's
11882        termination from the PGN tags, as the game list builder does
11883        not process the game moves.  --mann
11884        */
11885     cmailMsgLoaded = TRUE;
11886     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11887
11888     /* Load first game in the file or popup game menu */
11889     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11890
11891 #endif /* !WIN32 */
11892     return;
11893 }
11894
11895 int
11896 RegisterMove()
11897 {
11898     FILE *f;
11899     char string[MSG_SIZ];
11900
11901     if (   cmailMailedMove
11902         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11903         return TRUE;            /* Allow free viewing  */
11904     }
11905
11906     /* Unregister move to ensure that we don't leave RegisterMove        */
11907     /* with the move registered when the conditions for registering no   */
11908     /* longer hold                                                       */
11909     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11910         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11911         nCmailMovesRegistered --;
11912
11913         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11914           {
11915               free(cmailCommentList[lastLoadGameNumber - 1]);
11916               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11917           }
11918     }
11919
11920     if (cmailOldMove == -1) {
11921         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11922         return FALSE;
11923     }
11924
11925     if (currentMove > cmailOldMove + 1) {
11926         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11927         return FALSE;
11928     }
11929
11930     if (currentMove < cmailOldMove) {
11931         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11932         return FALSE;
11933     }
11934
11935     if (forwardMostMove > currentMove) {
11936         /* Silently truncate extra moves */
11937         TruncateGame();
11938     }
11939
11940     if (   (currentMove == cmailOldMove + 1)
11941         || (   (currentMove == cmailOldMove)
11942             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11943                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11944         if (gameInfo.result != GameUnfinished) {
11945             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11946         }
11947
11948         if (commentList[currentMove] != NULL) {
11949             cmailCommentList[lastLoadGameNumber - 1]
11950               = StrSave(commentList[currentMove]);
11951         }
11952         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11953
11954         if (appData.debugMode)
11955           fprintf(debugFP, "Saving %s for game %d\n",
11956                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11957
11958         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11959
11960         f = fopen(string, "w");
11961         if (appData.oldSaveStyle) {
11962             SaveGameOldStyle(f); /* also closes the file */
11963
11964             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11965             f = fopen(string, "w");
11966             SavePosition(f, 0, NULL); /* also closes the file */
11967         } else {
11968             fprintf(f, "{--------------\n");
11969             PrintPosition(f, currentMove);
11970             fprintf(f, "--------------}\n\n");
11971
11972             SaveGame(f, 0, NULL); /* also closes the file*/
11973         }
11974
11975         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11976         nCmailMovesRegistered ++;
11977     } else if (nCmailGames == 1) {
11978         DisplayError(_("You have not made a move yet"), 0);
11979         return FALSE;
11980     }
11981
11982     return TRUE;
11983 }
11984
11985 void
11986 MailMoveEvent()
11987 {
11988 #if !WIN32
11989     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11990     FILE *commandOutput;
11991     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11992     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11993     int nBuffers;
11994     int i;
11995     int archived;
11996     char *arcDir;
11997
11998     if (! cmailMsgLoaded) {
11999         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12000         return;
12001     }
12002
12003     if (nCmailGames == nCmailResults) {
12004         DisplayError(_("No unfinished games"), 0);
12005         return;
12006     }
12007
12008 #if CMAIL_PROHIBIT_REMAIL
12009     if (cmailMailedMove) {
12010       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);
12011         DisplayError(msg, 0);
12012         return;
12013     }
12014 #endif
12015
12016     if (! (cmailMailedMove || RegisterMove())) return;
12017
12018     if (   cmailMailedMove
12019         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12020       snprintf(string, MSG_SIZ, partCommandString,
12021                appData.debugMode ? " -v" : "", appData.cmailGameName);
12022         commandOutput = popen(string, "r");
12023
12024         if (commandOutput == NULL) {
12025             DisplayError(_("Failed to invoke cmail"), 0);
12026         } else {
12027             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12028                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12029             }
12030             if (nBuffers > 1) {
12031                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12032                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12033                 nBytes = MSG_SIZ - 1;
12034             } else {
12035                 (void) memcpy(msg, buffer, nBytes);
12036             }
12037             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12038
12039             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12040                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12041
12042                 archived = TRUE;
12043                 for (i = 0; i < nCmailGames; i ++) {
12044                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12045                         archived = FALSE;
12046                     }
12047                 }
12048                 if (   archived
12049                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12050                         != NULL)) {
12051                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12052                            arcDir,
12053                            appData.cmailGameName,
12054                            gameInfo.date);
12055                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12056                     cmailMsgLoaded = FALSE;
12057                 }
12058             }
12059
12060             DisplayInformation(msg);
12061             pclose(commandOutput);
12062         }
12063     } else {
12064         if ((*cmailMsg) != '\0') {
12065             DisplayInformation(cmailMsg);
12066         }
12067     }
12068
12069     return;
12070 #endif /* !WIN32 */
12071 }
12072
12073 char *
12074 CmailMsg()
12075 {
12076 #if WIN32
12077     return NULL;
12078 #else
12079     int  prependComma = 0;
12080     char number[5];
12081     char string[MSG_SIZ];       /* Space for game-list */
12082     int  i;
12083
12084     if (!cmailMsgLoaded) return "";
12085
12086     if (cmailMailedMove) {
12087       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12088     } else {
12089         /* Create a list of games left */
12090       snprintf(string, MSG_SIZ, "[");
12091         for (i = 0; i < nCmailGames; i ++) {
12092             if (! (   cmailMoveRegistered[i]
12093                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12094                 if (prependComma) {
12095                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12096                 } else {
12097                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12098                     prependComma = 1;
12099                 }
12100
12101                 strcat(string, number);
12102             }
12103         }
12104         strcat(string, "]");
12105
12106         if (nCmailMovesRegistered + nCmailResults == 0) {
12107             switch (nCmailGames) {
12108               case 1:
12109                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12110                 break;
12111
12112               case 2:
12113                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12114                 break;
12115
12116               default:
12117                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12118                          nCmailGames);
12119                 break;
12120             }
12121         } else {
12122             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12123               case 1:
12124                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12125                          string);
12126                 break;
12127
12128               case 0:
12129                 if (nCmailResults == nCmailGames) {
12130                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12131                 } else {
12132                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12133                 }
12134                 break;
12135
12136               default:
12137                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12138                          string);
12139             }
12140         }
12141     }
12142     return cmailMsg;
12143 #endif /* WIN32 */
12144 }
12145
12146 void
12147 ResetGameEvent()
12148 {
12149     if (gameMode == Training)
12150       SetTrainingModeOff();
12151
12152     Reset(TRUE, TRUE);
12153     cmailMsgLoaded = FALSE;
12154     if (appData.icsActive) {
12155       SendToICS(ics_prefix);
12156       SendToICS("refresh\n");
12157     }
12158 }
12159
12160 void
12161 ExitEvent(status)
12162      int status;
12163 {
12164     exiting++;
12165     if (exiting > 2) {
12166       /* Give up on clean exit */
12167       exit(status);
12168     }
12169     if (exiting > 1) {
12170       /* Keep trying for clean exit */
12171       return;
12172     }
12173
12174     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12175
12176     if (telnetISR != NULL) {
12177       RemoveInputSource(telnetISR);
12178     }
12179     if (icsPR != NoProc) {
12180       DestroyChildProcess(icsPR, TRUE);
12181     }
12182
12183     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12184     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12185
12186     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12187     /* make sure this other one finishes before killing it!                  */
12188     if(endingGame) { int count = 0;
12189         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12190         while(endingGame && count++ < 10) DoSleep(1);
12191         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12192     }
12193
12194     /* Kill off chess programs */
12195     if (first.pr != NoProc) {
12196         ExitAnalyzeMode();
12197
12198         DoSleep( appData.delayBeforeQuit );
12199         SendToProgram("quit\n", &first);
12200         DoSleep( appData.delayAfterQuit );
12201         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12202     }
12203     if (second.pr != NoProc) {
12204         DoSleep( appData.delayBeforeQuit );
12205         SendToProgram("quit\n", &second);
12206         DoSleep( appData.delayAfterQuit );
12207         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12208     }
12209     if (first.isr != NULL) {
12210         RemoveInputSource(first.isr);
12211     }
12212     if (second.isr != NULL) {
12213         RemoveInputSource(second.isr);
12214     }
12215
12216     ShutDownFrontEnd();
12217     exit(status);
12218 }
12219
12220 void
12221 PauseEvent()
12222 {
12223     if (appData.debugMode)
12224         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12225     if (pausing) {
12226         pausing = FALSE;
12227         ModeHighlight();
12228         if (gameMode == MachinePlaysWhite ||
12229             gameMode == MachinePlaysBlack) {
12230             StartClocks();
12231         } else {
12232             DisplayBothClocks();
12233         }
12234         if (gameMode == PlayFromGameFile) {
12235             if (appData.timeDelay >= 0)
12236                 AutoPlayGameLoop();
12237         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12238             Reset(FALSE, TRUE);
12239             SendToICS(ics_prefix);
12240             SendToICS("refresh\n");
12241         } else if (currentMove < forwardMostMove) {
12242             ForwardInner(forwardMostMove);
12243         }
12244         pauseExamInvalid = FALSE;
12245     } else {
12246         switch (gameMode) {
12247           default:
12248             return;
12249           case IcsExamining:
12250             pauseExamForwardMostMove = forwardMostMove;
12251             pauseExamInvalid = FALSE;
12252             /* fall through */
12253           case IcsObserving:
12254           case IcsPlayingWhite:
12255           case IcsPlayingBlack:
12256             pausing = TRUE;
12257             ModeHighlight();
12258             return;
12259           case PlayFromGameFile:
12260             (void) StopLoadGameTimer();
12261             pausing = TRUE;
12262             ModeHighlight();
12263             break;
12264           case BeginningOfGame:
12265             if (appData.icsActive) return;
12266             /* else fall through */
12267           case MachinePlaysWhite:
12268           case MachinePlaysBlack:
12269           case TwoMachinesPlay:
12270             if (forwardMostMove == 0)
12271               return;           /* don't pause if no one has moved */
12272             if ((gameMode == MachinePlaysWhite &&
12273                  !WhiteOnMove(forwardMostMove)) ||
12274                 (gameMode == MachinePlaysBlack &&
12275                  WhiteOnMove(forwardMostMove))) {
12276                 StopClocks();
12277             }
12278             pausing = TRUE;
12279             ModeHighlight();
12280             break;
12281         }
12282     }
12283 }
12284
12285 void
12286 EditCommentEvent()
12287 {
12288     char title[MSG_SIZ];
12289
12290     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12291       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12292     } else {
12293       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12294                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12295                parseList[currentMove - 1]);
12296     }
12297
12298     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12299 }
12300
12301
12302 void
12303 EditTagsEvent()
12304 {
12305     char *tags = PGNTags(&gameInfo);
12306     EditTagsPopUp(tags, NULL);
12307     free(tags);
12308 }
12309
12310 void
12311 AnalyzeModeEvent()
12312 {
12313     if (appData.noChessProgram || gameMode == AnalyzeMode)
12314       return;
12315
12316     if (gameMode != AnalyzeFile) {
12317         if (!appData.icsEngineAnalyze) {
12318                EditGameEvent();
12319                if (gameMode != EditGame) return;
12320         }
12321         ResurrectChessProgram();
12322         SendToProgram("analyze\n", &first);
12323         first.analyzing = TRUE;
12324         /*first.maybeThinking = TRUE;*/
12325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12326         EngineOutputPopUp();
12327     }
12328     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12329     pausing = FALSE;
12330     ModeHighlight();
12331     SetGameInfo();
12332
12333     StartAnalysisClock();
12334     GetTimeMark(&lastNodeCountTime);
12335     lastNodeCount = 0;
12336 }
12337
12338 void
12339 AnalyzeFileEvent()
12340 {
12341     if (appData.noChessProgram || gameMode == AnalyzeFile)
12342       return;
12343
12344     if (gameMode != AnalyzeMode) {
12345         EditGameEvent();
12346         if (gameMode != EditGame) return;
12347         ResurrectChessProgram();
12348         SendToProgram("analyze\n", &first);
12349         first.analyzing = TRUE;
12350         /*first.maybeThinking = TRUE;*/
12351         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12352         EngineOutputPopUp();
12353     }
12354     gameMode = AnalyzeFile;
12355     pausing = FALSE;
12356     ModeHighlight();
12357     SetGameInfo();
12358
12359     StartAnalysisClock();
12360     GetTimeMark(&lastNodeCountTime);
12361     lastNodeCount = 0;
12362 }
12363
12364 void
12365 MachineWhiteEvent()
12366 {
12367     char buf[MSG_SIZ];
12368     char *bookHit = NULL;
12369
12370     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12371       return;
12372
12373
12374     if (gameMode == PlayFromGameFile ||
12375         gameMode == TwoMachinesPlay  ||
12376         gameMode == Training         ||
12377         gameMode == AnalyzeMode      ||
12378         gameMode == EndOfGame)
12379         EditGameEvent();
12380
12381     if (gameMode == EditPosition)
12382         EditPositionDone(TRUE);
12383
12384     if (!WhiteOnMove(currentMove)) {
12385         DisplayError(_("It is not White's turn"), 0);
12386         return;
12387     }
12388
12389     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12390       ExitAnalyzeMode();
12391
12392     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12393         gameMode == AnalyzeFile)
12394         TruncateGame();
12395
12396     ResurrectChessProgram();    /* in case it isn't running */
12397     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12398         gameMode = MachinePlaysWhite;
12399         ResetClocks();
12400     } else
12401     gameMode = MachinePlaysWhite;
12402     pausing = FALSE;
12403     ModeHighlight();
12404     SetGameInfo();
12405     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12406     DisplayTitle(buf);
12407     if (first.sendName) {
12408       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12409       SendToProgram(buf, &first);
12410     }
12411     if (first.sendTime) {
12412       if (first.useColors) {
12413         SendToProgram("black\n", &first); /*gnu kludge*/
12414       }
12415       SendTimeRemaining(&first, TRUE);
12416     }
12417     if (first.useColors) {
12418       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12419     }
12420     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12421     SetMachineThinkingEnables();
12422     first.maybeThinking = TRUE;
12423     StartClocks();
12424     firstMove = FALSE;
12425
12426     if (appData.autoFlipView && !flipView) {
12427       flipView = !flipView;
12428       DrawPosition(FALSE, NULL);
12429       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12430     }
12431
12432     if(bookHit) { // [HGM] book: simulate book reply
12433         static char bookMove[MSG_SIZ]; // a bit generous?
12434
12435         programStats.nodes = programStats.depth = programStats.time =
12436         programStats.score = programStats.got_only_move = 0;
12437         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12438
12439         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12440         strcat(bookMove, bookHit);
12441         HandleMachineMove(bookMove, &first);
12442     }
12443 }
12444
12445 void
12446 MachineBlackEvent()
12447 {
12448   char buf[MSG_SIZ];
12449   char *bookHit = NULL;
12450
12451     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12452         return;
12453
12454
12455     if (gameMode == PlayFromGameFile ||
12456         gameMode == TwoMachinesPlay  ||
12457         gameMode == Training         ||
12458         gameMode == AnalyzeMode      ||
12459         gameMode == EndOfGame)
12460         EditGameEvent();
12461
12462     if (gameMode == EditPosition)
12463         EditPositionDone(TRUE);
12464
12465     if (WhiteOnMove(currentMove)) {
12466         DisplayError(_("It is not Black's turn"), 0);
12467         return;
12468     }
12469
12470     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12471       ExitAnalyzeMode();
12472
12473     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12474         gameMode == AnalyzeFile)
12475         TruncateGame();
12476
12477     ResurrectChessProgram();    /* in case it isn't running */
12478     gameMode = MachinePlaysBlack;
12479     pausing = FALSE;
12480     ModeHighlight();
12481     SetGameInfo();
12482     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12483     DisplayTitle(buf);
12484     if (first.sendName) {
12485       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12486       SendToProgram(buf, &first);
12487     }
12488     if (first.sendTime) {
12489       if (first.useColors) {
12490         SendToProgram("white\n", &first); /*gnu kludge*/
12491       }
12492       SendTimeRemaining(&first, FALSE);
12493     }
12494     if (first.useColors) {
12495       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12496     }
12497     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12498     SetMachineThinkingEnables();
12499     first.maybeThinking = TRUE;
12500     StartClocks();
12501
12502     if (appData.autoFlipView && flipView) {
12503       flipView = !flipView;
12504       DrawPosition(FALSE, NULL);
12505       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12506     }
12507     if(bookHit) { // [HGM] book: simulate book reply
12508         static char bookMove[MSG_SIZ]; // a bit generous?
12509
12510         programStats.nodes = programStats.depth = programStats.time =
12511         programStats.score = programStats.got_only_move = 0;
12512         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12513
12514         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12515         strcat(bookMove, bookHit);
12516         HandleMachineMove(bookMove, &first);
12517     }
12518 }
12519
12520
12521 void
12522 DisplayTwoMachinesTitle()
12523 {
12524     char buf[MSG_SIZ];
12525     if (appData.matchGames > 0) {
12526         if (first.twoMachinesColor[0] == 'w') {
12527           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12528                    gameInfo.white, gameInfo.black,
12529                    first.matchWins, second.matchWins,
12530                    matchGame - 1 - (first.matchWins + second.matchWins));
12531         } else {
12532           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12533                    gameInfo.white, gameInfo.black,
12534                    second.matchWins, first.matchWins,
12535                    matchGame - 1 - (first.matchWins + second.matchWins));
12536         }
12537     } else {
12538       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12539     }
12540     DisplayTitle(buf);
12541 }
12542
12543 void
12544 SettingsMenuIfReady()
12545 {
12546   if (second.lastPing != second.lastPong) {
12547     DisplayMessage("", _("Waiting for second chess program"));
12548     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12549     return;
12550   }
12551   ThawUI();
12552   DisplayMessage("", "");
12553   SettingsPopUp(&second);
12554 }
12555
12556 int
12557 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12558 {
12559     char buf[MSG_SIZ];
12560     if (cps->pr == NULL) {
12561         StartChessProgram(cps);
12562         if (cps->protocolVersion == 1) {
12563           retry();
12564         } else {
12565           /* kludge: allow timeout for initial "feature" command */
12566           FreezeUI();
12567           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12568           DisplayMessage("", buf);
12569           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12570         }
12571         return 1;
12572     }
12573     return 0;
12574 }
12575
12576 void
12577 TwoMachinesEvent P((void))
12578 {
12579     int i;
12580     char buf[MSG_SIZ];
12581     ChessProgramState *onmove;
12582     char *bookHit = NULL;
12583     static int stalling = 0;
12584     TimeMark now;
12585     long wait;
12586
12587     if (appData.noChessProgram) return;
12588
12589     switch (gameMode) {
12590       case TwoMachinesPlay:
12591         return;
12592       case MachinePlaysWhite:
12593       case MachinePlaysBlack:
12594         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12595             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12596             return;
12597         }
12598         /* fall through */
12599       case BeginningOfGame:
12600       case PlayFromGameFile:
12601       case EndOfGame:
12602         EditGameEvent();
12603         if (gameMode != EditGame) return;
12604         break;
12605       case EditPosition:
12606         EditPositionDone(TRUE);
12607         break;
12608       case AnalyzeMode:
12609       case AnalyzeFile:
12610         ExitAnalyzeMode();
12611         break;
12612       case EditGame:
12613       default:
12614         break;
12615     }
12616
12617 //    forwardMostMove = currentMove;
12618     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12619
12620     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12621
12622     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12623     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12624       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12625       return;
12626     }
12627     if(!stalling) {
12628       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12629       SendToProgram("force\n", &second);
12630       stalling = 1;
12631       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12632       return;
12633     }
12634     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12635     if(appData.matchPause>10000 || appData.matchPause<10)
12636                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12637     wait = SubtractTimeMarks(&now, &pauseStart);
12638     if(wait < appData.matchPause) {
12639         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12640         return;
12641     }
12642     stalling = 0;
12643     DisplayMessage("", "");
12644     if (startedFromSetupPosition) {
12645         SendBoard(&second, backwardMostMove);
12646     if (appData.debugMode) {
12647         fprintf(debugFP, "Two Machines\n");
12648     }
12649     }
12650     for (i = backwardMostMove; i < forwardMostMove; i++) {
12651         SendMoveToProgram(i, &second);
12652     }
12653
12654     gameMode = TwoMachinesPlay;
12655     pausing = FALSE;
12656     ModeHighlight();
12657     SetGameInfo();
12658     DisplayTwoMachinesTitle();
12659     firstMove = TRUE;
12660     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12661         onmove = &first;
12662     } else {
12663         onmove = &second;
12664     }
12665     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12666     SendToProgram(first.computerString, &first);
12667     if (first.sendName) {
12668       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12669       SendToProgram(buf, &first);
12670     }
12671     SendToProgram(second.computerString, &second);
12672     if (second.sendName) {
12673       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12674       SendToProgram(buf, &second);
12675     }
12676
12677     ResetClocks();
12678     if (!first.sendTime || !second.sendTime) {
12679         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12680         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12681     }
12682     if (onmove->sendTime) {
12683       if (onmove->useColors) {
12684         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12685       }
12686       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12687     }
12688     if (onmove->useColors) {
12689       SendToProgram(onmove->twoMachinesColor, onmove);
12690     }
12691     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12692 //    SendToProgram("go\n", onmove);
12693     onmove->maybeThinking = TRUE;
12694     SetMachineThinkingEnables();
12695
12696     StartClocks();
12697
12698     if(bookHit) { // [HGM] book: simulate book reply
12699         static char bookMove[MSG_SIZ]; // a bit generous?
12700
12701         programStats.nodes = programStats.depth = programStats.time =
12702         programStats.score = programStats.got_only_move = 0;
12703         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12704
12705         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12706         strcat(bookMove, bookHit);
12707         savedMessage = bookMove; // args for deferred call
12708         savedState = onmove;
12709         ScheduleDelayedEvent(DeferredBookMove, 1);
12710     }
12711 }
12712
12713 void
12714 TrainingEvent()
12715 {
12716     if (gameMode == Training) {
12717       SetTrainingModeOff();
12718       gameMode = PlayFromGameFile;
12719       DisplayMessage("", _("Training mode off"));
12720     } else {
12721       gameMode = Training;
12722       animateTraining = appData.animate;
12723
12724       /* make sure we are not already at the end of the game */
12725       if (currentMove < forwardMostMove) {
12726         SetTrainingModeOn();
12727         DisplayMessage("", _("Training mode on"));
12728       } else {
12729         gameMode = PlayFromGameFile;
12730         DisplayError(_("Already at end of game"), 0);
12731       }
12732     }
12733     ModeHighlight();
12734 }
12735
12736 void
12737 IcsClientEvent()
12738 {
12739     if (!appData.icsActive) return;
12740     switch (gameMode) {
12741       case IcsPlayingWhite:
12742       case IcsPlayingBlack:
12743       case IcsObserving:
12744       case IcsIdle:
12745       case BeginningOfGame:
12746       case IcsExamining:
12747         return;
12748
12749       case EditGame:
12750         break;
12751
12752       case EditPosition:
12753         EditPositionDone(TRUE);
12754         break;
12755
12756       case AnalyzeMode:
12757       case AnalyzeFile:
12758         ExitAnalyzeMode();
12759         break;
12760
12761       default:
12762         EditGameEvent();
12763         break;
12764     }
12765
12766     gameMode = IcsIdle;
12767     ModeHighlight();
12768     return;
12769 }
12770
12771
12772 void
12773 EditGameEvent()
12774 {
12775     int i;
12776
12777     switch (gameMode) {
12778       case Training:
12779         SetTrainingModeOff();
12780         break;
12781       case MachinePlaysWhite:
12782       case MachinePlaysBlack:
12783       case BeginningOfGame:
12784         SendToProgram("force\n", &first);
12785         SetUserThinkingEnables();
12786         break;
12787       case PlayFromGameFile:
12788         (void) StopLoadGameTimer();
12789         if (gameFileFP != NULL) {
12790             gameFileFP = NULL;
12791         }
12792         break;
12793       case EditPosition:
12794         EditPositionDone(TRUE);
12795         break;
12796       case AnalyzeMode:
12797       case AnalyzeFile:
12798         ExitAnalyzeMode();
12799         SendToProgram("force\n", &first);
12800         break;
12801       case TwoMachinesPlay:
12802         GameEnds(EndOfFile, NULL, GE_PLAYER);
12803         ResurrectChessProgram();
12804         SetUserThinkingEnables();
12805         break;
12806       case EndOfGame:
12807         ResurrectChessProgram();
12808         break;
12809       case IcsPlayingBlack:
12810       case IcsPlayingWhite:
12811         DisplayError(_("Warning: You are still playing a game"), 0);
12812         break;
12813       case IcsObserving:
12814         DisplayError(_("Warning: You are still observing a game"), 0);
12815         break;
12816       case IcsExamining:
12817         DisplayError(_("Warning: You are still examining a game"), 0);
12818         break;
12819       case IcsIdle:
12820         break;
12821       case EditGame:
12822       default:
12823         return;
12824     }
12825
12826     pausing = FALSE;
12827     StopClocks();
12828     first.offeredDraw = second.offeredDraw = 0;
12829
12830     if (gameMode == PlayFromGameFile) {
12831         whiteTimeRemaining = timeRemaining[0][currentMove];
12832         blackTimeRemaining = timeRemaining[1][currentMove];
12833         DisplayTitle("");
12834     }
12835
12836     if (gameMode == MachinePlaysWhite ||
12837         gameMode == MachinePlaysBlack ||
12838         gameMode == TwoMachinesPlay ||
12839         gameMode == EndOfGame) {
12840         i = forwardMostMove;
12841         while (i > currentMove) {
12842             SendToProgram("undo\n", &first);
12843             i--;
12844         }
12845         whiteTimeRemaining = timeRemaining[0][currentMove];
12846         blackTimeRemaining = timeRemaining[1][currentMove];
12847         DisplayBothClocks();
12848         if (whiteFlag || blackFlag) {
12849             whiteFlag = blackFlag = 0;
12850         }
12851         DisplayTitle("");
12852     }
12853
12854     gameMode = EditGame;
12855     ModeHighlight();
12856     SetGameInfo();
12857 }
12858
12859
12860 void
12861 EditPositionEvent()
12862 {
12863     if (gameMode == EditPosition) {
12864         EditGameEvent();
12865         return;
12866     }
12867
12868     EditGameEvent();
12869     if (gameMode != EditGame) return;
12870
12871     gameMode = EditPosition;
12872     ModeHighlight();
12873     SetGameInfo();
12874     if (currentMove > 0)
12875       CopyBoard(boards[0], boards[currentMove]);
12876
12877     blackPlaysFirst = !WhiteOnMove(currentMove);
12878     ResetClocks();
12879     currentMove = forwardMostMove = backwardMostMove = 0;
12880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12881     DisplayMove(-1);
12882 }
12883
12884 void
12885 ExitAnalyzeMode()
12886 {
12887     /* [DM] icsEngineAnalyze - possible call from other functions */
12888     if (appData.icsEngineAnalyze) {
12889         appData.icsEngineAnalyze = FALSE;
12890
12891         DisplayMessage("",_("Close ICS engine analyze..."));
12892     }
12893     if (first.analysisSupport && first.analyzing) {
12894       SendToProgram("exit\n", &first);
12895       first.analyzing = FALSE;
12896     }
12897     thinkOutput[0] = NULLCHAR;
12898 }
12899
12900 void
12901 EditPositionDone(Boolean fakeRights)
12902 {
12903     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12904
12905     startedFromSetupPosition = TRUE;
12906     InitChessProgram(&first, FALSE);
12907     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12908       boards[0][EP_STATUS] = EP_NONE;
12909       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12910     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12911         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12912         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12913       } else boards[0][CASTLING][2] = NoRights;
12914     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12915         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12916         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12917       } else boards[0][CASTLING][5] = NoRights;
12918     }
12919     SendToProgram("force\n", &first);
12920     if (blackPlaysFirst) {
12921         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12922         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12923         currentMove = forwardMostMove = backwardMostMove = 1;
12924         CopyBoard(boards[1], boards[0]);
12925     } else {
12926         currentMove = forwardMostMove = backwardMostMove = 0;
12927     }
12928     SendBoard(&first, forwardMostMove);
12929     if (appData.debugMode) {
12930         fprintf(debugFP, "EditPosDone\n");
12931     }
12932     DisplayTitle("");
12933     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12934     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12935     gameMode = EditGame;
12936     ModeHighlight();
12937     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12938     ClearHighlights(); /* [AS] */
12939 }
12940
12941 /* Pause for `ms' milliseconds */
12942 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12943 void
12944 TimeDelay(ms)
12945      long ms;
12946 {
12947     TimeMark m1, m2;
12948
12949     GetTimeMark(&m1);
12950     do {
12951         GetTimeMark(&m2);
12952     } while (SubtractTimeMarks(&m2, &m1) < ms);
12953 }
12954
12955 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12956 void
12957 SendMultiLineToICS(buf)
12958      char *buf;
12959 {
12960     char temp[MSG_SIZ+1], *p;
12961     int len;
12962
12963     len = strlen(buf);
12964     if (len > MSG_SIZ)
12965       len = MSG_SIZ;
12966
12967     strncpy(temp, buf, len);
12968     temp[len] = 0;
12969
12970     p = temp;
12971     while (*p) {
12972         if (*p == '\n' || *p == '\r')
12973           *p = ' ';
12974         ++p;
12975     }
12976
12977     strcat(temp, "\n");
12978     SendToICS(temp);
12979     SendToPlayer(temp, strlen(temp));
12980 }
12981
12982 void
12983 SetWhiteToPlayEvent()
12984 {
12985     if (gameMode == EditPosition) {
12986         blackPlaysFirst = FALSE;
12987         DisplayBothClocks();    /* works because currentMove is 0 */
12988     } else if (gameMode == IcsExamining) {
12989         SendToICS(ics_prefix);
12990         SendToICS("tomove white\n");
12991     }
12992 }
12993
12994 void
12995 SetBlackToPlayEvent()
12996 {
12997     if (gameMode == EditPosition) {
12998         blackPlaysFirst = TRUE;
12999         currentMove = 1;        /* kludge */
13000         DisplayBothClocks();
13001         currentMove = 0;
13002     } else if (gameMode == IcsExamining) {
13003         SendToICS(ics_prefix);
13004         SendToICS("tomove black\n");
13005     }
13006 }
13007
13008 void
13009 EditPositionMenuEvent(selection, x, y)
13010      ChessSquare selection;
13011      int x, y;
13012 {
13013     char buf[MSG_SIZ];
13014     ChessSquare piece = boards[0][y][x];
13015
13016     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13017
13018     switch (selection) {
13019       case ClearBoard:
13020         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13021             SendToICS(ics_prefix);
13022             SendToICS("bsetup clear\n");
13023         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13024             SendToICS(ics_prefix);
13025             SendToICS("clearboard\n");
13026         } else {
13027             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13028                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13029                 for (y = 0; y < BOARD_HEIGHT; y++) {
13030                     if (gameMode == IcsExamining) {
13031                         if (boards[currentMove][y][x] != EmptySquare) {
13032                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13033                                     AAA + x, ONE + y);
13034                             SendToICS(buf);
13035                         }
13036                     } else {
13037                         boards[0][y][x] = p;
13038                     }
13039                 }
13040             }
13041         }
13042         if (gameMode == EditPosition) {
13043             DrawPosition(FALSE, boards[0]);
13044         }
13045         break;
13046
13047       case WhitePlay:
13048         SetWhiteToPlayEvent();
13049         break;
13050
13051       case BlackPlay:
13052         SetBlackToPlayEvent();
13053         break;
13054
13055       case EmptySquare:
13056         if (gameMode == IcsExamining) {
13057             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13058             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13059             SendToICS(buf);
13060         } else {
13061             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13062                 if(x == BOARD_LEFT-2) {
13063                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13064                     boards[0][y][1] = 0;
13065                 } else
13066                 if(x == BOARD_RGHT+1) {
13067                     if(y >= gameInfo.holdingsSize) break;
13068                     boards[0][y][BOARD_WIDTH-2] = 0;
13069                 } else break;
13070             }
13071             boards[0][y][x] = EmptySquare;
13072             DrawPosition(FALSE, boards[0]);
13073         }
13074         break;
13075
13076       case PromotePiece:
13077         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13078            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13079             selection = (ChessSquare) (PROMOTED piece);
13080         } else if(piece == EmptySquare) selection = WhiteSilver;
13081         else selection = (ChessSquare)((int)piece - 1);
13082         goto defaultlabel;
13083
13084       case DemotePiece:
13085         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13086            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13087             selection = (ChessSquare) (DEMOTED piece);
13088         } else if(piece == EmptySquare) selection = BlackSilver;
13089         else selection = (ChessSquare)((int)piece + 1);
13090         goto defaultlabel;
13091
13092       case WhiteQueen:
13093       case BlackQueen:
13094         if(gameInfo.variant == VariantShatranj ||
13095            gameInfo.variant == VariantXiangqi  ||
13096            gameInfo.variant == VariantCourier  ||
13097            gameInfo.variant == VariantMakruk     )
13098             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13099         goto defaultlabel;
13100
13101       case WhiteKing:
13102       case BlackKing:
13103         if(gameInfo.variant == VariantXiangqi)
13104             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13105         if(gameInfo.variant == VariantKnightmate)
13106             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13107       default:
13108         defaultlabel:
13109         if (gameMode == IcsExamining) {
13110             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13111             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13112                      PieceToChar(selection), AAA + x, ONE + y);
13113             SendToICS(buf);
13114         } else {
13115             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13116                 int n;
13117                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13118                     n = PieceToNumber(selection - BlackPawn);
13119                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13120                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13121                     boards[0][BOARD_HEIGHT-1-n][1]++;
13122                 } else
13123                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13124                     n = PieceToNumber(selection);
13125                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13126                     boards[0][n][BOARD_WIDTH-1] = selection;
13127                     boards[0][n][BOARD_WIDTH-2]++;
13128                 }
13129             } else
13130             boards[0][y][x] = selection;
13131             DrawPosition(TRUE, boards[0]);
13132         }
13133         break;
13134     }
13135 }
13136
13137
13138 void
13139 DropMenuEvent(selection, x, y)
13140      ChessSquare selection;
13141      int x, y;
13142 {
13143     ChessMove moveType;
13144
13145     switch (gameMode) {
13146       case IcsPlayingWhite:
13147       case MachinePlaysBlack:
13148         if (!WhiteOnMove(currentMove)) {
13149             DisplayMoveError(_("It is Black's turn"));
13150             return;
13151         }
13152         moveType = WhiteDrop;
13153         break;
13154       case IcsPlayingBlack:
13155       case MachinePlaysWhite:
13156         if (WhiteOnMove(currentMove)) {
13157             DisplayMoveError(_("It is White's turn"));
13158             return;
13159         }
13160         moveType = BlackDrop;
13161         break;
13162       case EditGame:
13163         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13164         break;
13165       default:
13166         return;
13167     }
13168
13169     if (moveType == BlackDrop && selection < BlackPawn) {
13170       selection = (ChessSquare) ((int) selection
13171                                  + (int) BlackPawn - (int) WhitePawn);
13172     }
13173     if (boards[currentMove][y][x] != EmptySquare) {
13174         DisplayMoveError(_("That square is occupied"));
13175         return;
13176     }
13177
13178     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13179 }
13180
13181 void
13182 AcceptEvent()
13183 {
13184     /* Accept a pending offer of any kind from opponent */
13185
13186     if (appData.icsActive) {
13187         SendToICS(ics_prefix);
13188         SendToICS("accept\n");
13189     } else if (cmailMsgLoaded) {
13190         if (currentMove == cmailOldMove &&
13191             commentList[cmailOldMove] != NULL &&
13192             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13193                    "Black offers a draw" : "White offers a draw")) {
13194             TruncateGame();
13195             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13196             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13197         } else {
13198             DisplayError(_("There is no pending offer on this move"), 0);
13199             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13200         }
13201     } else {
13202         /* Not used for offers from chess program */
13203     }
13204 }
13205
13206 void
13207 DeclineEvent()
13208 {
13209     /* Decline a pending offer of any kind from opponent */
13210
13211     if (appData.icsActive) {
13212         SendToICS(ics_prefix);
13213         SendToICS("decline\n");
13214     } else if (cmailMsgLoaded) {
13215         if (currentMove == cmailOldMove &&
13216             commentList[cmailOldMove] != NULL &&
13217             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13218                    "Black offers a draw" : "White offers a draw")) {
13219 #ifdef NOTDEF
13220             AppendComment(cmailOldMove, "Draw declined", TRUE);
13221             DisplayComment(cmailOldMove - 1, "Draw declined");
13222 #endif /*NOTDEF*/
13223         } else {
13224             DisplayError(_("There is no pending offer on this move"), 0);
13225         }
13226     } else {
13227         /* Not used for offers from chess program */
13228     }
13229 }
13230
13231 void
13232 RematchEvent()
13233 {
13234     /* Issue ICS rematch command */
13235     if (appData.icsActive) {
13236         SendToICS(ics_prefix);
13237         SendToICS("rematch\n");
13238     }
13239 }
13240
13241 void
13242 CallFlagEvent()
13243 {
13244     /* Call your opponent's flag (claim a win on time) */
13245     if (appData.icsActive) {
13246         SendToICS(ics_prefix);
13247         SendToICS("flag\n");
13248     } else {
13249         switch (gameMode) {
13250           default:
13251             return;
13252           case MachinePlaysWhite:
13253             if (whiteFlag) {
13254                 if (blackFlag)
13255                   GameEnds(GameIsDrawn, "Both players ran out of time",
13256                            GE_PLAYER);
13257                 else
13258                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13259             } else {
13260                 DisplayError(_("Your opponent is not out of time"), 0);
13261             }
13262             break;
13263           case MachinePlaysBlack:
13264             if (blackFlag) {
13265                 if (whiteFlag)
13266                   GameEnds(GameIsDrawn, "Both players ran out of time",
13267                            GE_PLAYER);
13268                 else
13269                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13270             } else {
13271                 DisplayError(_("Your opponent is not out of time"), 0);
13272             }
13273             break;
13274         }
13275     }
13276 }
13277
13278 void
13279 ClockClick(int which)
13280 {       // [HGM] code moved to back-end from winboard.c
13281         if(which) { // black clock
13282           if (gameMode == EditPosition || gameMode == IcsExamining) {
13283             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13284             SetBlackToPlayEvent();
13285           } else if (gameMode == EditGame || shiftKey) {
13286             AdjustClock(which, -1);
13287           } else if (gameMode == IcsPlayingWhite ||
13288                      gameMode == MachinePlaysBlack) {
13289             CallFlagEvent();
13290           }
13291         } else { // white clock
13292           if (gameMode == EditPosition || gameMode == IcsExamining) {
13293             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13294             SetWhiteToPlayEvent();
13295           } else if (gameMode == EditGame || shiftKey) {
13296             AdjustClock(which, -1);
13297           } else if (gameMode == IcsPlayingBlack ||
13298                    gameMode == MachinePlaysWhite) {
13299             CallFlagEvent();
13300           }
13301         }
13302 }
13303
13304 void
13305 DrawEvent()
13306 {
13307     /* Offer draw or accept pending draw offer from opponent */
13308
13309     if (appData.icsActive) {
13310         /* Note: tournament rules require draw offers to be
13311            made after you make your move but before you punch
13312            your clock.  Currently ICS doesn't let you do that;
13313            instead, you immediately punch your clock after making
13314            a move, but you can offer a draw at any time. */
13315
13316         SendToICS(ics_prefix);
13317         SendToICS("draw\n");
13318         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13319     } else if (cmailMsgLoaded) {
13320         if (currentMove == cmailOldMove &&
13321             commentList[cmailOldMove] != NULL &&
13322             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13323                    "Black offers a draw" : "White offers a draw")) {
13324             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13325             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13326         } else if (currentMove == cmailOldMove + 1) {
13327             char *offer = WhiteOnMove(cmailOldMove) ?
13328               "White offers a draw" : "Black offers a draw";
13329             AppendComment(currentMove, offer, TRUE);
13330             DisplayComment(currentMove - 1, offer);
13331             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13332         } else {
13333             DisplayError(_("You must make your move before offering a draw"), 0);
13334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13335         }
13336     } else if (first.offeredDraw) {
13337         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13338     } else {
13339         if (first.sendDrawOffers) {
13340             SendToProgram("draw\n", &first);
13341             userOfferedDraw = TRUE;
13342         }
13343     }
13344 }
13345
13346 void
13347 AdjournEvent()
13348 {
13349     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13350
13351     if (appData.icsActive) {
13352         SendToICS(ics_prefix);
13353         SendToICS("adjourn\n");
13354     } else {
13355         /* Currently GNU Chess doesn't offer or accept Adjourns */
13356     }
13357 }
13358
13359
13360 void
13361 AbortEvent()
13362 {
13363     /* Offer Abort or accept pending Abort offer from opponent */
13364
13365     if (appData.icsActive) {
13366         SendToICS(ics_prefix);
13367         SendToICS("abort\n");
13368     } else {
13369         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13370     }
13371 }
13372
13373 void
13374 ResignEvent()
13375 {
13376     /* Resign.  You can do this even if it's not your turn. */
13377
13378     if (appData.icsActive) {
13379         SendToICS(ics_prefix);
13380         SendToICS("resign\n");
13381     } else {
13382         switch (gameMode) {
13383           case MachinePlaysWhite:
13384             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13385             break;
13386           case MachinePlaysBlack:
13387             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13388             break;
13389           case EditGame:
13390             if (cmailMsgLoaded) {
13391                 TruncateGame();
13392                 if (WhiteOnMove(cmailOldMove)) {
13393                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13394                 } else {
13395                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13396                 }
13397                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13398             }
13399             break;
13400           default:
13401             break;
13402         }
13403     }
13404 }
13405
13406
13407 void
13408 StopObservingEvent()
13409 {
13410     /* Stop observing current games */
13411     SendToICS(ics_prefix);
13412     SendToICS("unobserve\n");
13413 }
13414
13415 void
13416 StopExaminingEvent()
13417 {
13418     /* Stop observing current game */
13419     SendToICS(ics_prefix);
13420     SendToICS("unexamine\n");
13421 }
13422
13423 void
13424 ForwardInner(target)
13425      int target;
13426 {
13427     int limit;
13428
13429     if (appData.debugMode)
13430         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13431                 target, currentMove, forwardMostMove);
13432
13433     if (gameMode == EditPosition)
13434       return;
13435
13436     if (gameMode == PlayFromGameFile && !pausing)
13437       PauseEvent();
13438
13439     if (gameMode == IcsExamining && pausing)
13440       limit = pauseExamForwardMostMove;
13441     else
13442       limit = forwardMostMove;
13443
13444     if (target > limit) target = limit;
13445
13446     if (target > 0 && moveList[target - 1][0]) {
13447         int fromX, fromY, toX, toY;
13448         toX = moveList[target - 1][2] - AAA;
13449         toY = moveList[target - 1][3] - ONE;
13450         if (moveList[target - 1][1] == '@') {
13451             if (appData.highlightLastMove) {
13452                 SetHighlights(-1, -1, toX, toY);
13453             }
13454         } else {
13455             fromX = moveList[target - 1][0] - AAA;
13456             fromY = moveList[target - 1][1] - ONE;
13457             if (target == currentMove + 1) {
13458                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13459             }
13460             if (appData.highlightLastMove) {
13461                 SetHighlights(fromX, fromY, toX, toY);
13462             }
13463         }
13464     }
13465     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13466         gameMode == Training || gameMode == PlayFromGameFile ||
13467         gameMode == AnalyzeFile) {
13468         while (currentMove < target) {
13469             SendMoveToProgram(currentMove++, &first);
13470         }
13471     } else {
13472         currentMove = target;
13473     }
13474
13475     if (gameMode == EditGame || gameMode == EndOfGame) {
13476         whiteTimeRemaining = timeRemaining[0][currentMove];
13477         blackTimeRemaining = timeRemaining[1][currentMove];
13478     }
13479     DisplayBothClocks();
13480     DisplayMove(currentMove - 1);
13481     DrawPosition(FALSE, boards[currentMove]);
13482     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13483     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13484         DisplayComment(currentMove - 1, commentList[currentMove]);
13485     }
13486 }
13487
13488
13489 void
13490 ForwardEvent()
13491 {
13492     if (gameMode == IcsExamining && !pausing) {
13493         SendToICS(ics_prefix);
13494         SendToICS("forward\n");
13495     } else {
13496         ForwardInner(currentMove + 1);
13497     }
13498 }
13499
13500 void
13501 ToEndEvent()
13502 {
13503     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13504         /* to optimze, we temporarily turn off analysis mode while we feed
13505          * the remaining moves to the engine. Otherwise we get analysis output
13506          * after each move.
13507          */
13508         if (first.analysisSupport) {
13509           SendToProgram("exit\nforce\n", &first);
13510           first.analyzing = FALSE;
13511         }
13512     }
13513
13514     if (gameMode == IcsExamining && !pausing) {
13515         SendToICS(ics_prefix);
13516         SendToICS("forward 999999\n");
13517     } else {
13518         ForwardInner(forwardMostMove);
13519     }
13520
13521     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13522         /* we have fed all the moves, so reactivate analysis mode */
13523         SendToProgram("analyze\n", &first);
13524         first.analyzing = TRUE;
13525         /*first.maybeThinking = TRUE;*/
13526         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13527     }
13528 }
13529
13530 void
13531 BackwardInner(target)
13532      int target;
13533 {
13534     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13535
13536     if (appData.debugMode)
13537         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13538                 target, currentMove, forwardMostMove);
13539
13540     if (gameMode == EditPosition) return;
13541     if (currentMove <= backwardMostMove) {
13542         ClearHighlights();
13543         DrawPosition(full_redraw, boards[currentMove]);
13544         return;
13545     }
13546     if (gameMode == PlayFromGameFile && !pausing)
13547       PauseEvent();
13548
13549     if (moveList[target][0]) {
13550         int fromX, fromY, toX, toY;
13551         toX = moveList[target][2] - AAA;
13552         toY = moveList[target][3] - ONE;
13553         if (moveList[target][1] == '@') {
13554             if (appData.highlightLastMove) {
13555                 SetHighlights(-1, -1, toX, toY);
13556             }
13557         } else {
13558             fromX = moveList[target][0] - AAA;
13559             fromY = moveList[target][1] - ONE;
13560             if (target == currentMove - 1) {
13561                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13562             }
13563             if (appData.highlightLastMove) {
13564                 SetHighlights(fromX, fromY, toX, toY);
13565             }
13566         }
13567     }
13568     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13569         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13570         while (currentMove > target) {
13571             SendToProgram("undo\n", &first);
13572             currentMove--;
13573         }
13574     } else {
13575         currentMove = target;
13576     }
13577
13578     if (gameMode == EditGame || gameMode == EndOfGame) {
13579         whiteTimeRemaining = timeRemaining[0][currentMove];
13580         blackTimeRemaining = timeRemaining[1][currentMove];
13581     }
13582     DisplayBothClocks();
13583     DisplayMove(currentMove - 1);
13584     DrawPosition(full_redraw, boards[currentMove]);
13585     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13586     // [HGM] PV info: routine tests if comment empty
13587     DisplayComment(currentMove - 1, commentList[currentMove]);
13588 }
13589
13590 void
13591 BackwardEvent()
13592 {
13593     if (gameMode == IcsExamining && !pausing) {
13594         SendToICS(ics_prefix);
13595         SendToICS("backward\n");
13596     } else {
13597         BackwardInner(currentMove - 1);
13598     }
13599 }
13600
13601 void
13602 ToStartEvent()
13603 {
13604     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13605         /* to optimize, we temporarily turn off analysis mode while we undo
13606          * all the moves. Otherwise we get analysis output after each undo.
13607          */
13608         if (first.analysisSupport) {
13609           SendToProgram("exit\nforce\n", &first);
13610           first.analyzing = FALSE;
13611         }
13612     }
13613
13614     if (gameMode == IcsExamining && !pausing) {
13615         SendToICS(ics_prefix);
13616         SendToICS("backward 999999\n");
13617     } else {
13618         BackwardInner(backwardMostMove);
13619     }
13620
13621     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13622         /* we have fed all the moves, so reactivate analysis mode */
13623         SendToProgram("analyze\n", &first);
13624         first.analyzing = TRUE;
13625         /*first.maybeThinking = TRUE;*/
13626         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13627     }
13628 }
13629
13630 void
13631 ToNrEvent(int to)
13632 {
13633   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13634   if (to >= forwardMostMove) to = forwardMostMove;
13635   if (to <= backwardMostMove) to = backwardMostMove;
13636   if (to < currentMove) {
13637     BackwardInner(to);
13638   } else {
13639     ForwardInner(to);
13640   }
13641 }
13642
13643 void
13644 RevertEvent(Boolean annotate)
13645 {
13646     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13647         return;
13648     }
13649     if (gameMode != IcsExamining) {
13650         DisplayError(_("You are not examining a game"), 0);
13651         return;
13652     }
13653     if (pausing) {
13654         DisplayError(_("You can't revert while pausing"), 0);
13655         return;
13656     }
13657     SendToICS(ics_prefix);
13658     SendToICS("revert\n");
13659 }
13660
13661 void
13662 RetractMoveEvent()
13663 {
13664     switch (gameMode) {
13665       case MachinePlaysWhite:
13666       case MachinePlaysBlack:
13667         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13668             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13669             return;
13670         }
13671         if (forwardMostMove < 2) return;
13672         currentMove = forwardMostMove = forwardMostMove - 2;
13673         whiteTimeRemaining = timeRemaining[0][currentMove];
13674         blackTimeRemaining = timeRemaining[1][currentMove];
13675         DisplayBothClocks();
13676         DisplayMove(currentMove - 1);
13677         ClearHighlights();/*!! could figure this out*/
13678         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13679         SendToProgram("remove\n", &first);
13680         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13681         break;
13682
13683       case BeginningOfGame:
13684       default:
13685         break;
13686
13687       case IcsPlayingWhite:
13688       case IcsPlayingBlack:
13689         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13690             SendToICS(ics_prefix);
13691             SendToICS("takeback 2\n");
13692         } else {
13693             SendToICS(ics_prefix);
13694             SendToICS("takeback 1\n");
13695         }
13696         break;
13697     }
13698 }
13699
13700 void
13701 MoveNowEvent()
13702 {
13703     ChessProgramState *cps;
13704
13705     switch (gameMode) {
13706       case MachinePlaysWhite:
13707         if (!WhiteOnMove(forwardMostMove)) {
13708             DisplayError(_("It is your turn"), 0);
13709             return;
13710         }
13711         cps = &first;
13712         break;
13713       case MachinePlaysBlack:
13714         if (WhiteOnMove(forwardMostMove)) {
13715             DisplayError(_("It is your turn"), 0);
13716             return;
13717         }
13718         cps = &first;
13719         break;
13720       case TwoMachinesPlay:
13721         if (WhiteOnMove(forwardMostMove) ==
13722             (first.twoMachinesColor[0] == 'w')) {
13723             cps = &first;
13724         } else {
13725             cps = &second;
13726         }
13727         break;
13728       case BeginningOfGame:
13729       default:
13730         return;
13731     }
13732     SendToProgram("?\n", cps);
13733 }
13734
13735 void
13736 TruncateGameEvent()
13737 {
13738     EditGameEvent();
13739     if (gameMode != EditGame) return;
13740     TruncateGame();
13741 }
13742
13743 void
13744 TruncateGame()
13745 {
13746     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13747     if (forwardMostMove > currentMove) {
13748         if (gameInfo.resultDetails != NULL) {
13749             free(gameInfo.resultDetails);
13750             gameInfo.resultDetails = NULL;
13751             gameInfo.result = GameUnfinished;
13752         }
13753         forwardMostMove = currentMove;
13754         HistorySet(parseList, backwardMostMove, forwardMostMove,
13755                    currentMove-1);
13756     }
13757 }
13758
13759 void
13760 HintEvent()
13761 {
13762     if (appData.noChessProgram) return;
13763     switch (gameMode) {
13764       case MachinePlaysWhite:
13765         if (WhiteOnMove(forwardMostMove)) {
13766             DisplayError(_("Wait until your turn"), 0);
13767             return;
13768         }
13769         break;
13770       case BeginningOfGame:
13771       case MachinePlaysBlack:
13772         if (!WhiteOnMove(forwardMostMove)) {
13773             DisplayError(_("Wait until your turn"), 0);
13774             return;
13775         }
13776         break;
13777       default:
13778         DisplayError(_("No hint available"), 0);
13779         return;
13780     }
13781     SendToProgram("hint\n", &first);
13782     hintRequested = TRUE;
13783 }
13784
13785 void
13786 BookEvent()
13787 {
13788     if (appData.noChessProgram) return;
13789     switch (gameMode) {
13790       case MachinePlaysWhite:
13791         if (WhiteOnMove(forwardMostMove)) {
13792             DisplayError(_("Wait until your turn"), 0);
13793             return;
13794         }
13795         break;
13796       case BeginningOfGame:
13797       case MachinePlaysBlack:
13798         if (!WhiteOnMove(forwardMostMove)) {
13799             DisplayError(_("Wait until your turn"), 0);
13800             return;
13801         }
13802         break;
13803       case EditPosition:
13804         EditPositionDone(TRUE);
13805         break;
13806       case TwoMachinesPlay:
13807         return;
13808       default:
13809         break;
13810     }
13811     SendToProgram("bk\n", &first);
13812     bookOutput[0] = NULLCHAR;
13813     bookRequested = TRUE;
13814 }
13815
13816 void
13817 AboutGameEvent()
13818 {
13819     char *tags = PGNTags(&gameInfo);
13820     TagsPopUp(tags, CmailMsg());
13821     free(tags);
13822 }
13823
13824 /* end button procedures */
13825
13826 void
13827 PrintPosition(fp, move)
13828      FILE *fp;
13829      int move;
13830 {
13831     int i, j;
13832
13833     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13834         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13835             char c = PieceToChar(boards[move][i][j]);
13836             fputc(c == 'x' ? '.' : c, fp);
13837             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13838         }
13839     }
13840     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13841       fprintf(fp, "white to play\n");
13842     else
13843       fprintf(fp, "black to play\n");
13844 }
13845
13846 void
13847 PrintOpponents(fp)
13848      FILE *fp;
13849 {
13850     if (gameInfo.white != NULL) {
13851         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13852     } else {
13853         fprintf(fp, "\n");
13854     }
13855 }
13856
13857 /* Find last component of program's own name, using some heuristics */
13858 void
13859 TidyProgramName(prog, host, buf)
13860      char *prog, *host, buf[MSG_SIZ];
13861 {
13862     char *p, *q;
13863     int local = (strcmp(host, "localhost") == 0);
13864     while (!local && (p = strchr(prog, ';')) != NULL) {
13865         p++;
13866         while (*p == ' ') p++;
13867         prog = p;
13868     }
13869     if (*prog == '"' || *prog == '\'') {
13870         q = strchr(prog + 1, *prog);
13871     } else {
13872         q = strchr(prog, ' ');
13873     }
13874     if (q == NULL) q = prog + strlen(prog);
13875     p = q;
13876     while (p >= prog && *p != '/' && *p != '\\') p--;
13877     p++;
13878     if(p == prog && *p == '"') p++;
13879     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13880     memcpy(buf, p, q - p);
13881     buf[q - p] = NULLCHAR;
13882     if (!local) {
13883         strcat(buf, "@");
13884         strcat(buf, host);
13885     }
13886 }
13887
13888 char *
13889 TimeControlTagValue()
13890 {
13891     char buf[MSG_SIZ];
13892     if (!appData.clockMode) {
13893       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13894     } else if (movesPerSession > 0) {
13895       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13896     } else if (timeIncrement == 0) {
13897       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13898     } else {
13899       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13900     }
13901     return StrSave(buf);
13902 }
13903
13904 void
13905 SetGameInfo()
13906 {
13907     /* This routine is used only for certain modes */
13908     VariantClass v = gameInfo.variant;
13909     ChessMove r = GameUnfinished;
13910     char *p = NULL;
13911
13912     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13913         r = gameInfo.result;
13914         p = gameInfo.resultDetails;
13915         gameInfo.resultDetails = NULL;
13916     }
13917     ClearGameInfo(&gameInfo);
13918     gameInfo.variant = v;
13919
13920     switch (gameMode) {
13921       case MachinePlaysWhite:
13922         gameInfo.event = StrSave( appData.pgnEventHeader );
13923         gameInfo.site = StrSave(HostName());
13924         gameInfo.date = PGNDate();
13925         gameInfo.round = StrSave("-");
13926         gameInfo.white = StrSave(first.tidy);
13927         gameInfo.black = StrSave(UserName());
13928         gameInfo.timeControl = TimeControlTagValue();
13929         break;
13930
13931       case MachinePlaysBlack:
13932         gameInfo.event = StrSave( appData.pgnEventHeader );
13933         gameInfo.site = StrSave(HostName());
13934         gameInfo.date = PGNDate();
13935         gameInfo.round = StrSave("-");
13936         gameInfo.white = StrSave(UserName());
13937         gameInfo.black = StrSave(first.tidy);
13938         gameInfo.timeControl = TimeControlTagValue();
13939         break;
13940
13941       case TwoMachinesPlay:
13942         gameInfo.event = StrSave( appData.pgnEventHeader );
13943         gameInfo.site = StrSave(HostName());
13944         gameInfo.date = PGNDate();
13945         if (roundNr > 0) {
13946             char buf[MSG_SIZ];
13947             snprintf(buf, MSG_SIZ, "%d", roundNr);
13948             gameInfo.round = StrSave(buf);
13949         } else {
13950             gameInfo.round = StrSave("-");
13951         }
13952         if (first.twoMachinesColor[0] == 'w') {
13953             gameInfo.white = StrSave(first.tidy);
13954             gameInfo.black = StrSave(second.tidy);
13955         } else {
13956             gameInfo.white = StrSave(second.tidy);
13957             gameInfo.black = StrSave(first.tidy);
13958         }
13959         gameInfo.timeControl = TimeControlTagValue();
13960         break;
13961
13962       case EditGame:
13963         gameInfo.event = StrSave("Edited game");
13964         gameInfo.site = StrSave(HostName());
13965         gameInfo.date = PGNDate();
13966         gameInfo.round = StrSave("-");
13967         gameInfo.white = StrSave("-");
13968         gameInfo.black = StrSave("-");
13969         gameInfo.result = r;
13970         gameInfo.resultDetails = p;
13971         break;
13972
13973       case EditPosition:
13974         gameInfo.event = StrSave("Edited position");
13975         gameInfo.site = StrSave(HostName());
13976         gameInfo.date = PGNDate();
13977         gameInfo.round = StrSave("-");
13978         gameInfo.white = StrSave("-");
13979         gameInfo.black = StrSave("-");
13980         break;
13981
13982       case IcsPlayingWhite:
13983       case IcsPlayingBlack:
13984       case IcsObserving:
13985       case IcsExamining:
13986         break;
13987
13988       case PlayFromGameFile:
13989         gameInfo.event = StrSave("Game from non-PGN file");
13990         gameInfo.site = StrSave(HostName());
13991         gameInfo.date = PGNDate();
13992         gameInfo.round = StrSave("-");
13993         gameInfo.white = StrSave("?");
13994         gameInfo.black = StrSave("?");
13995         break;
13996
13997       default:
13998         break;
13999     }
14000 }
14001
14002 void
14003 ReplaceComment(index, text)
14004      int index;
14005      char *text;
14006 {
14007     int len;
14008     char *p;
14009     float score;
14010
14011     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14012        pvInfoList[index-1].depth == len &&
14013        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14014        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14015     while (*text == '\n') text++;
14016     len = strlen(text);
14017     while (len > 0 && text[len - 1] == '\n') len--;
14018
14019     if (commentList[index] != NULL)
14020       free(commentList[index]);
14021
14022     if (len == 0) {
14023         commentList[index] = NULL;
14024         return;
14025     }
14026   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14027       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14028       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14029     commentList[index] = (char *) malloc(len + 2);
14030     strncpy(commentList[index], text, len);
14031     commentList[index][len] = '\n';
14032     commentList[index][len + 1] = NULLCHAR;
14033   } else {
14034     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14035     char *p;
14036     commentList[index] = (char *) malloc(len + 7);
14037     safeStrCpy(commentList[index], "{\n", 3);
14038     safeStrCpy(commentList[index]+2, text, len+1);
14039     commentList[index][len+2] = NULLCHAR;
14040     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14041     strcat(commentList[index], "\n}\n");
14042   }
14043 }
14044
14045 void
14046 CrushCRs(text)
14047      char *text;
14048 {
14049   char *p = text;
14050   char *q = text;
14051   char ch;
14052
14053   do {
14054     ch = *p++;
14055     if (ch == '\r') continue;
14056     *q++ = ch;
14057   } while (ch != '\0');
14058 }
14059
14060 void
14061 AppendComment(index, text, addBraces)
14062      int index;
14063      char *text;
14064      Boolean addBraces; // [HGM] braces: tells if we should add {}
14065 {
14066     int oldlen, len;
14067     char *old;
14068
14069 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14070     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14071
14072     CrushCRs(text);
14073     while (*text == '\n') text++;
14074     len = strlen(text);
14075     while (len > 0 && text[len - 1] == '\n') len--;
14076
14077     if (len == 0) return;
14078
14079     if (commentList[index] != NULL) {
14080         old = commentList[index];
14081         oldlen = strlen(old);
14082         while(commentList[index][oldlen-1] ==  '\n')
14083           commentList[index][--oldlen] = NULLCHAR;
14084         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14085         safeStrCpy(commentList[index], old, oldlen + len + 6);
14086         free(old);
14087         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14088         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14089           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14090           while (*text == '\n') { text++; len--; }
14091           commentList[index][--oldlen] = NULLCHAR;
14092       }
14093         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14094         else          strcat(commentList[index], "\n");
14095         strcat(commentList[index], text);
14096         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14097         else          strcat(commentList[index], "\n");
14098     } else {
14099         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14100         if(addBraces)
14101           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14102         else commentList[index][0] = NULLCHAR;
14103         strcat(commentList[index], text);
14104         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14105         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14106     }
14107 }
14108
14109 static char * FindStr( char * text, char * sub_text )
14110 {
14111     char * result = strstr( text, sub_text );
14112
14113     if( result != NULL ) {
14114         result += strlen( sub_text );
14115     }
14116
14117     return result;
14118 }
14119
14120 /* [AS] Try to extract PV info from PGN comment */
14121 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14122 char *GetInfoFromComment( int index, char * text )
14123 {
14124     char * sep = text, *p;
14125
14126     if( text != NULL && index > 0 ) {
14127         int score = 0;
14128         int depth = 0;
14129         int time = -1, sec = 0, deci;
14130         char * s_eval = FindStr( text, "[%eval " );
14131         char * s_emt = FindStr( text, "[%emt " );
14132
14133         if( s_eval != NULL || s_emt != NULL ) {
14134             /* New style */
14135             char delim;
14136
14137             if( s_eval != NULL ) {
14138                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14139                     return text;
14140                 }
14141
14142                 if( delim != ']' ) {
14143                     return text;
14144                 }
14145             }
14146
14147             if( s_emt != NULL ) {
14148             }
14149                 return text;
14150         }
14151         else {
14152             /* We expect something like: [+|-]nnn.nn/dd */
14153             int score_lo = 0;
14154
14155             if(*text != '{') return text; // [HGM] braces: must be normal comment
14156
14157             sep = strchr( text, '/' );
14158             if( sep == NULL || sep < (text+4) ) {
14159                 return text;
14160             }
14161
14162             p = text;
14163             if(p[1] == '(') { // comment starts with PV
14164                p = strchr(p, ')'); // locate end of PV
14165                if(p == NULL || sep < p+5) return text;
14166                // at this point we have something like "{(.*) +0.23/6 ..."
14167                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14168                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14169                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14170             }
14171             time = -1; sec = -1; deci = -1;
14172             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14173                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14174                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14175                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14176                 return text;
14177             }
14178
14179             if( score_lo < 0 || score_lo >= 100 ) {
14180                 return text;
14181             }
14182
14183             if(sec >= 0) time = 600*time + 10*sec; else
14184             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14185
14186             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14187
14188             /* [HGM] PV time: now locate end of PV info */
14189             while( *++sep >= '0' && *sep <= '9'); // strip depth
14190             if(time >= 0)
14191             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14192             if(sec >= 0)
14193             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14194             if(deci >= 0)
14195             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14196             while(*sep == ' ') sep++;
14197         }
14198
14199         if( depth <= 0 ) {
14200             return text;
14201         }
14202
14203         if( time < 0 ) {
14204             time = -1;
14205         }
14206
14207         pvInfoList[index-1].depth = depth;
14208         pvInfoList[index-1].score = score;
14209         pvInfoList[index-1].time  = 10*time; // centi-sec
14210         if(*sep == '}') *sep = 0; else *--sep = '{';
14211         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14212     }
14213     return sep;
14214 }
14215
14216 void
14217 SendToProgram(message, cps)
14218      char *message;
14219      ChessProgramState *cps;
14220 {
14221     int count, outCount, error;
14222     char buf[MSG_SIZ];
14223
14224     if (cps->pr == NULL) return;
14225     Attention(cps);
14226
14227     if (appData.debugMode) {
14228         TimeMark now;
14229         GetTimeMark(&now);
14230         fprintf(debugFP, "%ld >%-6s: %s",
14231                 SubtractTimeMarks(&now, &programStartTime),
14232                 cps->which, message);
14233     }
14234
14235     count = strlen(message);
14236     outCount = OutputToProcess(cps->pr, message, count, &error);
14237     if (outCount < count && !exiting
14238                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14239       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14240       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14241         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14242             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14243                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14244                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14245                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14246             } else {
14247                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14248                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14249                 gameInfo.result = res;
14250             }
14251             gameInfo.resultDetails = StrSave(buf);
14252         }
14253         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14254         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14255     }
14256 }
14257
14258 void
14259 ReceiveFromProgram(isr, closure, message, count, error)
14260      InputSourceRef isr;
14261      VOIDSTAR closure;
14262      char *message;
14263      int count;
14264      int error;
14265 {
14266     char *end_str;
14267     char buf[MSG_SIZ];
14268     ChessProgramState *cps = (ChessProgramState *)closure;
14269
14270     if (isr != cps->isr) return; /* Killed intentionally */
14271     if (count <= 0) {
14272         if (count == 0) {
14273             RemoveInputSource(cps->isr);
14274             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14275                     _(cps->which), cps->program);
14276         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14277                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14278                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14279                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14280                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14281                 } else {
14282                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14283                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14284                     gameInfo.result = res;
14285                 }
14286                 gameInfo.resultDetails = StrSave(buf);
14287             }
14288             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14289             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14290         } else {
14291             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14292                     _(cps->which), cps->program);
14293             RemoveInputSource(cps->isr);
14294
14295             /* [AS] Program is misbehaving badly... kill it */
14296             if( count == -2 ) {
14297                 DestroyChildProcess( cps->pr, 9 );
14298                 cps->pr = NoProc;
14299             }
14300
14301             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14302         }
14303         return;
14304     }
14305
14306     if ((end_str = strchr(message, '\r')) != NULL)
14307       *end_str = NULLCHAR;
14308     if ((end_str = strchr(message, '\n')) != NULL)
14309       *end_str = NULLCHAR;
14310
14311     if (appData.debugMode) {
14312         TimeMark now; int print = 1;
14313         char *quote = ""; char c; int i;
14314
14315         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14316                 char start = message[0];
14317                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14318                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14319                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14320                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14321                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14322                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14323                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14324                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14325                    sscanf(message, "hint: %c", &c)!=1 && 
14326                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14327                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14328                     print = (appData.engineComments >= 2);
14329                 }
14330                 message[0] = start; // restore original message
14331         }
14332         if(print) {
14333                 GetTimeMark(&now);
14334                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14335                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14336                         quote,
14337                         message);
14338         }
14339     }
14340
14341     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14342     if (appData.icsEngineAnalyze) {
14343         if (strstr(message, "whisper") != NULL ||
14344              strstr(message, "kibitz") != NULL ||
14345             strstr(message, "tellics") != NULL) return;
14346     }
14347
14348     HandleMachineMove(message, cps);
14349 }
14350
14351
14352 void
14353 SendTimeControl(cps, mps, tc, inc, sd, st)
14354      ChessProgramState *cps;
14355      int mps, inc, sd, st;
14356      long tc;
14357 {
14358     char buf[MSG_SIZ];
14359     int seconds;
14360
14361     if( timeControl_2 > 0 ) {
14362         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14363             tc = timeControl_2;
14364         }
14365     }
14366     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14367     inc /= cps->timeOdds;
14368     st  /= cps->timeOdds;
14369
14370     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14371
14372     if (st > 0) {
14373       /* Set exact time per move, normally using st command */
14374       if (cps->stKludge) {
14375         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14376         seconds = st % 60;
14377         if (seconds == 0) {
14378           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14379         } else {
14380           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14381         }
14382       } else {
14383         snprintf(buf, MSG_SIZ, "st %d\n", st);
14384       }
14385     } else {
14386       /* Set conventional or incremental time control, using level command */
14387       if (seconds == 0) {
14388         /* Note old gnuchess bug -- minutes:seconds used to not work.
14389            Fixed in later versions, but still avoid :seconds
14390            when seconds is 0. */
14391         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14392       } else {
14393         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14394                  seconds, inc/1000.);
14395       }
14396     }
14397     SendToProgram(buf, cps);
14398
14399     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14400     /* Orthogonally, limit search to given depth */
14401     if (sd > 0) {
14402       if (cps->sdKludge) {
14403         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14404       } else {
14405         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14406       }
14407       SendToProgram(buf, cps);
14408     }
14409
14410     if(cps->nps >= 0) { /* [HGM] nps */
14411         if(cps->supportsNPS == FALSE)
14412           cps->nps = -1; // don't use if engine explicitly says not supported!
14413         else {
14414           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14415           SendToProgram(buf, cps);
14416         }
14417     }
14418 }
14419
14420 ChessProgramState *WhitePlayer()
14421 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14422 {
14423     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14424        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14425         return &second;
14426     return &first;
14427 }
14428
14429 void
14430 SendTimeRemaining(cps, machineWhite)
14431      ChessProgramState *cps;
14432      int /*boolean*/ machineWhite;
14433 {
14434     char message[MSG_SIZ];
14435     long time, otime;
14436
14437     /* Note: this routine must be called when the clocks are stopped
14438        or when they have *just* been set or switched; otherwise
14439        it will be off by the time since the current tick started.
14440     */
14441     if (machineWhite) {
14442         time = whiteTimeRemaining / 10;
14443         otime = blackTimeRemaining / 10;
14444     } else {
14445         time = blackTimeRemaining / 10;
14446         otime = whiteTimeRemaining / 10;
14447     }
14448     /* [HGM] translate opponent's time by time-odds factor */
14449     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14450     if (appData.debugMode) {
14451         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14452     }
14453
14454     if (time <= 0) time = 1;
14455     if (otime <= 0) otime = 1;
14456
14457     snprintf(message, MSG_SIZ, "time %ld\n", time);
14458     SendToProgram(message, cps);
14459
14460     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14461     SendToProgram(message, cps);
14462 }
14463
14464 int
14465 BoolFeature(p, name, loc, cps)
14466      char **p;
14467      char *name;
14468      int *loc;
14469      ChessProgramState *cps;
14470 {
14471   char buf[MSG_SIZ];
14472   int len = strlen(name);
14473   int val;
14474
14475   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14476     (*p) += len + 1;
14477     sscanf(*p, "%d", &val);
14478     *loc = (val != 0);
14479     while (**p && **p != ' ')
14480       (*p)++;
14481     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14482     SendToProgram(buf, cps);
14483     return TRUE;
14484   }
14485   return FALSE;
14486 }
14487
14488 int
14489 IntFeature(p, name, loc, cps)
14490      char **p;
14491      char *name;
14492      int *loc;
14493      ChessProgramState *cps;
14494 {
14495   char buf[MSG_SIZ];
14496   int len = strlen(name);
14497   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14498     (*p) += len + 1;
14499     sscanf(*p, "%d", loc);
14500     while (**p && **p != ' ') (*p)++;
14501     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14502     SendToProgram(buf, cps);
14503     return TRUE;
14504   }
14505   return FALSE;
14506 }
14507
14508 int
14509 StringFeature(p, name, loc, cps)
14510      char **p;
14511      char *name;
14512      char loc[];
14513      ChessProgramState *cps;
14514 {
14515   char buf[MSG_SIZ];
14516   int len = strlen(name);
14517   if (strncmp((*p), name, len) == 0
14518       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14519     (*p) += len + 2;
14520     sscanf(*p, "%[^\"]", loc);
14521     while (**p && **p != '\"') (*p)++;
14522     if (**p == '\"') (*p)++;
14523     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14524     SendToProgram(buf, cps);
14525     return TRUE;
14526   }
14527   return FALSE;
14528 }
14529
14530 int
14531 ParseOption(Option *opt, ChessProgramState *cps)
14532 // [HGM] options: process the string that defines an engine option, and determine
14533 // name, type, default value, and allowed value range
14534 {
14535         char *p, *q, buf[MSG_SIZ];
14536         int n, min = (-1)<<31, max = 1<<31, def;
14537
14538         if(p = strstr(opt->name, " -spin ")) {
14539             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14540             if(max < min) max = min; // enforce consistency
14541             if(def < min) def = min;
14542             if(def > max) def = max;
14543             opt->value = def;
14544             opt->min = min;
14545             opt->max = max;
14546             opt->type = Spin;
14547         } else if((p = strstr(opt->name, " -slider "))) {
14548             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14549             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14550             if(max < min) max = min; // enforce consistency
14551             if(def < min) def = min;
14552             if(def > max) def = max;
14553             opt->value = def;
14554             opt->min = min;
14555             opt->max = max;
14556             opt->type = Spin; // Slider;
14557         } else if((p = strstr(opt->name, " -string "))) {
14558             opt->textValue = p+9;
14559             opt->type = TextBox;
14560         } else if((p = strstr(opt->name, " -file "))) {
14561             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14562             opt->textValue = p+7;
14563             opt->type = FileName; // FileName;
14564         } else if((p = strstr(opt->name, " -path "))) {
14565             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14566             opt->textValue = p+7;
14567             opt->type = PathName; // PathName;
14568         } else if(p = strstr(opt->name, " -check ")) {
14569             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14570             opt->value = (def != 0);
14571             opt->type = CheckBox;
14572         } else if(p = strstr(opt->name, " -combo ")) {
14573             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14574             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14575             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14576             opt->value = n = 0;
14577             while(q = StrStr(q, " /// ")) {
14578                 n++; *q = 0;    // count choices, and null-terminate each of them
14579                 q += 5;
14580                 if(*q == '*') { // remember default, which is marked with * prefix
14581                     q++;
14582                     opt->value = n;
14583                 }
14584                 cps->comboList[cps->comboCnt++] = q;
14585             }
14586             cps->comboList[cps->comboCnt++] = NULL;
14587             opt->max = n + 1;
14588             opt->type = ComboBox;
14589         } else if(p = strstr(opt->name, " -button")) {
14590             opt->type = Button;
14591         } else if(p = strstr(opt->name, " -save")) {
14592             opt->type = SaveButton;
14593         } else return FALSE;
14594         *p = 0; // terminate option name
14595         // now look if the command-line options define a setting for this engine option.
14596         if(cps->optionSettings && cps->optionSettings[0])
14597             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14598         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14599           snprintf(buf, MSG_SIZ, "option %s", p);
14600                 if(p = strstr(buf, ",")) *p = 0;
14601                 if(q = strchr(buf, '=')) switch(opt->type) {
14602                     case ComboBox:
14603                         for(n=0; n<opt->max; n++)
14604                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14605                         break;
14606                     case TextBox:
14607                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14608                         break;
14609                     case Spin:
14610                     case CheckBox:
14611                         opt->value = atoi(q+1);
14612                     default:
14613                         break;
14614                 }
14615                 strcat(buf, "\n");
14616                 SendToProgram(buf, cps);
14617         }
14618         return TRUE;
14619 }
14620
14621 void
14622 FeatureDone(cps, val)
14623      ChessProgramState* cps;
14624      int val;
14625 {
14626   DelayedEventCallback cb = GetDelayedEvent();
14627   if ((cb == InitBackEnd3 && cps == &first) ||
14628       (cb == SettingsMenuIfReady && cps == &second) ||
14629       (cb == LoadEngine) ||
14630       (cb == TwoMachinesEventIfReady)) {
14631     CancelDelayedEvent();
14632     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14633   }
14634   cps->initDone = val;
14635 }
14636
14637 /* Parse feature command from engine */
14638 void
14639 ParseFeatures(args, cps)
14640      char* args;
14641      ChessProgramState *cps;
14642 {
14643   char *p = args;
14644   char *q;
14645   int val;
14646   char buf[MSG_SIZ];
14647
14648   for (;;) {
14649     while (*p == ' ') p++;
14650     if (*p == NULLCHAR) return;
14651
14652     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14653     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14654     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14655     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14656     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14657     if (BoolFeature(&p, "reuse", &val, cps)) {
14658       /* Engine can disable reuse, but can't enable it if user said no */
14659       if (!val) cps->reuse = FALSE;
14660       continue;
14661     }
14662     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14663     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14664       if (gameMode == TwoMachinesPlay) {
14665         DisplayTwoMachinesTitle();
14666       } else {
14667         DisplayTitle("");
14668       }
14669       continue;
14670     }
14671     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14672     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14673     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14674     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14675     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14676     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14677     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14678     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14679     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14680     if (IntFeature(&p, "done", &val, cps)) {
14681       FeatureDone(cps, val);
14682       continue;
14683     }
14684     /* Added by Tord: */
14685     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14686     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14687     /* End of additions by Tord */
14688
14689     /* [HGM] added features: */
14690     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14691     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14692     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14693     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14694     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14695     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14696     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14697         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14698           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14699             SendToProgram(buf, cps);
14700             continue;
14701         }
14702         if(cps->nrOptions >= MAX_OPTIONS) {
14703             cps->nrOptions--;
14704             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14705             DisplayError(buf, 0);
14706         }
14707         continue;
14708     }
14709     /* End of additions by HGM */
14710
14711     /* unknown feature: complain and skip */
14712     q = p;
14713     while (*q && *q != '=') q++;
14714     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14715     SendToProgram(buf, cps);
14716     p = q;
14717     if (*p == '=') {
14718       p++;
14719       if (*p == '\"') {
14720         p++;
14721         while (*p && *p != '\"') p++;
14722         if (*p == '\"') p++;
14723       } else {
14724         while (*p && *p != ' ') p++;
14725       }
14726     }
14727   }
14728
14729 }
14730
14731 void
14732 PeriodicUpdatesEvent(newState)
14733      int newState;
14734 {
14735     if (newState == appData.periodicUpdates)
14736       return;
14737
14738     appData.periodicUpdates=newState;
14739
14740     /* Display type changes, so update it now */
14741 //    DisplayAnalysis();
14742
14743     /* Get the ball rolling again... */
14744     if (newState) {
14745         AnalysisPeriodicEvent(1);
14746         StartAnalysisClock();
14747     }
14748 }
14749
14750 void
14751 PonderNextMoveEvent(newState)
14752      int newState;
14753 {
14754     if (newState == appData.ponderNextMove) return;
14755     if (gameMode == EditPosition) EditPositionDone(TRUE);
14756     if (newState) {
14757         SendToProgram("hard\n", &first);
14758         if (gameMode == TwoMachinesPlay) {
14759             SendToProgram("hard\n", &second);
14760         }
14761     } else {
14762         SendToProgram("easy\n", &first);
14763         thinkOutput[0] = NULLCHAR;
14764         if (gameMode == TwoMachinesPlay) {
14765             SendToProgram("easy\n", &second);
14766         }
14767     }
14768     appData.ponderNextMove = newState;
14769 }
14770
14771 void
14772 NewSettingEvent(option, feature, command, value)
14773      char *command;
14774      int option, value, *feature;
14775 {
14776     char buf[MSG_SIZ];
14777
14778     if (gameMode == EditPosition) EditPositionDone(TRUE);
14779     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14780     if(feature == NULL || *feature) SendToProgram(buf, &first);
14781     if (gameMode == TwoMachinesPlay) {
14782         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14783     }
14784 }
14785
14786 void
14787 ShowThinkingEvent()
14788 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14789 {
14790     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14791     int newState = appData.showThinking
14792         // [HGM] thinking: other features now need thinking output as well
14793         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14794
14795     if (oldState == newState) return;
14796     oldState = newState;
14797     if (gameMode == EditPosition) EditPositionDone(TRUE);
14798     if (oldState) {
14799         SendToProgram("post\n", &first);
14800         if (gameMode == TwoMachinesPlay) {
14801             SendToProgram("post\n", &second);
14802         }
14803     } else {
14804         SendToProgram("nopost\n", &first);
14805         thinkOutput[0] = NULLCHAR;
14806         if (gameMode == TwoMachinesPlay) {
14807             SendToProgram("nopost\n", &second);
14808         }
14809     }
14810 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14811 }
14812
14813 void
14814 AskQuestionEvent(title, question, replyPrefix, which)
14815      char *title; char *question; char *replyPrefix; char *which;
14816 {
14817   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14818   if (pr == NoProc) return;
14819   AskQuestion(title, question, replyPrefix, pr);
14820 }
14821
14822 void
14823 TypeInEvent(char firstChar)
14824 {
14825     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14826         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14827         gameMode == AnalyzeMode || gameMode == EditGame || \r
14828         gameMode == EditPosition || gameMode == IcsExamining ||\r
14829         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14830         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14831                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14832                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14833         gameMode == Training) PopUpMoveDialog(firstChar);
14834 }
14835
14836 void
14837 TypeInDoneEvent(char *move)
14838 {
14839         Board board;
14840         int n, fromX, fromY, toX, toY;
14841         char promoChar;
14842         ChessMove moveType;\r
14843
14844         // [HGM] FENedit\r
14845         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14846                 EditPositionPasteFEN(move);\r
14847                 return;\r
14848         }\r
14849         // [HGM] movenum: allow move number to be typed in any mode\r
14850         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14851           ToNrEvent(2*n-1);\r
14852           return;\r
14853         }\r
14854
14855       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14856         gameMode != Training) {\r
14857         DisplayMoveError(_("Displayed move is not current"));\r
14858       } else {\r
14859         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14860           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14861         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14862         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14863           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14864           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14865         } else {\r
14866           DisplayMoveError(_("Could not parse move"));\r
14867         }
14868       }\r
14869 }\r
14870
14871 void
14872 DisplayMove(moveNumber)
14873      int moveNumber;
14874 {
14875     char message[MSG_SIZ];
14876     char res[MSG_SIZ];
14877     char cpThinkOutput[MSG_SIZ];
14878
14879     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14880
14881     if (moveNumber == forwardMostMove - 1 ||
14882         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14883
14884         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14885
14886         if (strchr(cpThinkOutput, '\n')) {
14887             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14888         }
14889     } else {
14890         *cpThinkOutput = NULLCHAR;
14891     }
14892
14893     /* [AS] Hide thinking from human user */
14894     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14895         *cpThinkOutput = NULLCHAR;
14896         if( thinkOutput[0] != NULLCHAR ) {
14897             int i;
14898
14899             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14900                 cpThinkOutput[i] = '.';
14901             }
14902             cpThinkOutput[i] = NULLCHAR;
14903             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14904         }
14905     }
14906
14907     if (moveNumber == forwardMostMove - 1 &&
14908         gameInfo.resultDetails != NULL) {
14909         if (gameInfo.resultDetails[0] == NULLCHAR) {
14910           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14911         } else {
14912           snprintf(res, MSG_SIZ, " {%s} %s",
14913                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14914         }
14915     } else {
14916         res[0] = NULLCHAR;
14917     }
14918
14919     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14920         DisplayMessage(res, cpThinkOutput);
14921     } else {
14922       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14923                 WhiteOnMove(moveNumber) ? " " : ".. ",
14924                 parseList[moveNumber], res);
14925         DisplayMessage(message, cpThinkOutput);
14926     }
14927 }
14928
14929 void
14930 DisplayComment(moveNumber, text)
14931      int moveNumber;
14932      char *text;
14933 {
14934     char title[MSG_SIZ];
14935     char buf[8000]; // comment can be long!
14936     int score, depth;
14937
14938     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14939       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14940     } else {
14941       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14942               WhiteOnMove(moveNumber) ? " " : ".. ",
14943               parseList[moveNumber]);
14944     }
14945     // [HGM] PV info: display PV info together with (or as) comment
14946     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14947       if(text == NULL) text = "";
14948       score = pvInfoList[moveNumber].score;
14949       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14950               depth, (pvInfoList[moveNumber].time+50)/100, text);
14951       text = buf;
14952     }
14953     if (text != NULL && (appData.autoDisplayComment || commentUp))
14954         CommentPopUp(title, text);
14955 }
14956
14957 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14958  * might be busy thinking or pondering.  It can be omitted if your
14959  * gnuchess is configured to stop thinking immediately on any user
14960  * input.  However, that gnuchess feature depends on the FIONREAD
14961  * ioctl, which does not work properly on some flavors of Unix.
14962  */
14963 void
14964 Attention(cps)
14965      ChessProgramState *cps;
14966 {
14967 #if ATTENTION
14968     if (!cps->useSigint) return;
14969     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14970     switch (gameMode) {
14971       case MachinePlaysWhite:
14972       case MachinePlaysBlack:
14973       case TwoMachinesPlay:
14974       case IcsPlayingWhite:
14975       case IcsPlayingBlack:
14976       case AnalyzeMode:
14977       case AnalyzeFile:
14978         /* Skip if we know it isn't thinking */
14979         if (!cps->maybeThinking) return;
14980         if (appData.debugMode)
14981           fprintf(debugFP, "Interrupting %s\n", cps->which);
14982         InterruptChildProcess(cps->pr);
14983         cps->maybeThinking = FALSE;
14984         break;
14985       default:
14986         break;
14987     }
14988 #endif /*ATTENTION*/
14989 }
14990
14991 int
14992 CheckFlags()
14993 {
14994     if (whiteTimeRemaining <= 0) {
14995         if (!whiteFlag) {
14996             whiteFlag = TRUE;
14997             if (appData.icsActive) {
14998                 if (appData.autoCallFlag &&
14999                     gameMode == IcsPlayingBlack && !blackFlag) {
15000                   SendToICS(ics_prefix);
15001                   SendToICS("flag\n");
15002                 }
15003             } else {
15004                 if (blackFlag) {
15005                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15006                 } else {
15007                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15008                     if (appData.autoCallFlag) {
15009                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15010                         return TRUE;
15011                     }
15012                 }
15013             }
15014         }
15015     }
15016     if (blackTimeRemaining <= 0) {
15017         if (!blackFlag) {
15018             blackFlag = TRUE;
15019             if (appData.icsActive) {
15020                 if (appData.autoCallFlag &&
15021                     gameMode == IcsPlayingWhite && !whiteFlag) {
15022                   SendToICS(ics_prefix);
15023                   SendToICS("flag\n");
15024                 }
15025             } else {
15026                 if (whiteFlag) {
15027                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15028                 } else {
15029                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15030                     if (appData.autoCallFlag) {
15031                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15032                         return TRUE;
15033                     }
15034                 }
15035             }
15036         }
15037     }
15038     return FALSE;
15039 }
15040
15041 void
15042 CheckTimeControl()
15043 {
15044     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15045         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15046
15047     /*
15048      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15049      */
15050     if ( !WhiteOnMove(forwardMostMove) ) {
15051         /* White made time control */
15052         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15053         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15054         /* [HGM] time odds: correct new time quota for time odds! */
15055                                             / WhitePlayer()->timeOdds;
15056         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15057     } else {
15058         lastBlack -= blackTimeRemaining;
15059         /* Black made time control */
15060         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15061                                             / WhitePlayer()->other->timeOdds;
15062         lastWhite = whiteTimeRemaining;
15063     }
15064 }
15065
15066 void
15067 DisplayBothClocks()
15068 {
15069     int wom = gameMode == EditPosition ?
15070       !blackPlaysFirst : WhiteOnMove(currentMove);
15071     DisplayWhiteClock(whiteTimeRemaining, wom);
15072     DisplayBlackClock(blackTimeRemaining, !wom);
15073 }
15074
15075
15076 /* Timekeeping seems to be a portability nightmare.  I think everyone
15077    has ftime(), but I'm really not sure, so I'm including some ifdefs
15078    to use other calls if you don't.  Clocks will be less accurate if
15079    you have neither ftime nor gettimeofday.
15080 */
15081
15082 /* VS 2008 requires the #include outside of the function */
15083 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15084 #include <sys/timeb.h>
15085 #endif
15086
15087 /* Get the current time as a TimeMark */
15088 void
15089 GetTimeMark(tm)
15090      TimeMark *tm;
15091 {
15092 #if HAVE_GETTIMEOFDAY
15093
15094     struct timeval timeVal;
15095     struct timezone timeZone;
15096
15097     gettimeofday(&timeVal, &timeZone);
15098     tm->sec = (long) timeVal.tv_sec;
15099     tm->ms = (int) (timeVal.tv_usec / 1000L);
15100
15101 #else /*!HAVE_GETTIMEOFDAY*/
15102 #if HAVE_FTIME
15103
15104 // include <sys/timeb.h> / moved to just above start of function
15105     struct timeb timeB;
15106
15107     ftime(&timeB);
15108     tm->sec = (long) timeB.time;
15109     tm->ms = (int) timeB.millitm;
15110
15111 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15112     tm->sec = (long) time(NULL);
15113     tm->ms = 0;
15114 #endif
15115 #endif
15116 }
15117
15118 /* Return the difference in milliseconds between two
15119    time marks.  We assume the difference will fit in a long!
15120 */
15121 long
15122 SubtractTimeMarks(tm2, tm1)
15123      TimeMark *tm2, *tm1;
15124 {
15125     return 1000L*(tm2->sec - tm1->sec) +
15126            (long) (tm2->ms - tm1->ms);
15127 }
15128
15129
15130 /*
15131  * Code to manage the game clocks.
15132  *
15133  * In tournament play, black starts the clock and then white makes a move.
15134  * We give the human user a slight advantage if he is playing white---the
15135  * clocks don't run until he makes his first move, so it takes zero time.
15136  * Also, we don't account for network lag, so we could get out of sync
15137  * with GNU Chess's clock -- but then, referees are always right.
15138  */
15139
15140 static TimeMark tickStartTM;
15141 static long intendedTickLength;
15142
15143 long
15144 NextTickLength(timeRemaining)
15145      long timeRemaining;
15146 {
15147     long nominalTickLength, nextTickLength;
15148
15149     if (timeRemaining > 0L && timeRemaining <= 10000L)
15150       nominalTickLength = 100L;
15151     else
15152       nominalTickLength = 1000L;
15153     nextTickLength = timeRemaining % nominalTickLength;
15154     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15155
15156     return nextTickLength;
15157 }
15158
15159 /* Adjust clock one minute up or down */
15160 void
15161 AdjustClock(Boolean which, int dir)
15162 {
15163     if(which) blackTimeRemaining += 60000*dir;
15164     else      whiteTimeRemaining += 60000*dir;
15165     DisplayBothClocks();
15166 }
15167
15168 /* Stop clocks and reset to a fresh time control */
15169 void
15170 ResetClocks()
15171 {
15172     (void) StopClockTimer();
15173     if (appData.icsActive) {
15174         whiteTimeRemaining = blackTimeRemaining = 0;
15175     } else if (searchTime) {
15176         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15177         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15178     } else { /* [HGM] correct new time quote for time odds */
15179         whiteTC = blackTC = fullTimeControlString;
15180         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15181         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15182     }
15183     if (whiteFlag || blackFlag) {
15184         DisplayTitle("");
15185         whiteFlag = blackFlag = FALSE;
15186     }
15187     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15188     DisplayBothClocks();
15189 }
15190
15191 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15192
15193 /* Decrement running clock by amount of time that has passed */
15194 void
15195 DecrementClocks()
15196 {
15197     long timeRemaining;
15198     long lastTickLength, fudge;
15199     TimeMark now;
15200
15201     if (!appData.clockMode) return;
15202     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15203
15204     GetTimeMark(&now);
15205
15206     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15207
15208     /* Fudge if we woke up a little too soon */
15209     fudge = intendedTickLength - lastTickLength;
15210     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15211
15212     if (WhiteOnMove(forwardMostMove)) {
15213         if(whiteNPS >= 0) lastTickLength = 0;
15214         timeRemaining = whiteTimeRemaining -= lastTickLength;
15215         if(timeRemaining < 0 && !appData.icsActive) {
15216             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15217             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15218                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15219                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15220             }
15221         }
15222         DisplayWhiteClock(whiteTimeRemaining - fudge,
15223                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15224     } else {
15225         if(blackNPS >= 0) lastTickLength = 0;
15226         timeRemaining = blackTimeRemaining -= lastTickLength;
15227         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15228             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15229             if(suddenDeath) {
15230                 blackStartMove = forwardMostMove;
15231                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15232             }
15233         }
15234         DisplayBlackClock(blackTimeRemaining - fudge,
15235                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15236     }
15237     if (CheckFlags()) return;
15238
15239     tickStartTM = now;
15240     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15241     StartClockTimer(intendedTickLength);
15242
15243     /* if the time remaining has fallen below the alarm threshold, sound the
15244      * alarm. if the alarm has sounded and (due to a takeback or time control
15245      * with increment) the time remaining has increased to a level above the
15246      * threshold, reset the alarm so it can sound again.
15247      */
15248
15249     if (appData.icsActive && appData.icsAlarm) {
15250
15251         /* make sure we are dealing with the user's clock */
15252         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15253                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15254            )) return;
15255
15256         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15257             alarmSounded = FALSE;
15258         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15259             PlayAlarmSound();
15260             alarmSounded = TRUE;
15261         }
15262     }
15263 }
15264
15265
15266 /* A player has just moved, so stop the previously running
15267    clock and (if in clock mode) start the other one.
15268    We redisplay both clocks in case we're in ICS mode, because
15269    ICS gives us an update to both clocks after every move.
15270    Note that this routine is called *after* forwardMostMove
15271    is updated, so the last fractional tick must be subtracted
15272    from the color that is *not* on move now.
15273 */
15274 void
15275 SwitchClocks(int newMoveNr)
15276 {
15277     long lastTickLength;
15278     TimeMark now;
15279     int flagged = FALSE;
15280
15281     GetTimeMark(&now);
15282
15283     if (StopClockTimer() && appData.clockMode) {
15284         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15285         if (!WhiteOnMove(forwardMostMove)) {
15286             if(blackNPS >= 0) lastTickLength = 0;
15287             blackTimeRemaining -= lastTickLength;
15288            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15289 //         if(pvInfoList[forwardMostMove].time == -1)
15290                  pvInfoList[forwardMostMove].time =               // use GUI time
15291                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15292         } else {
15293            if(whiteNPS >= 0) lastTickLength = 0;
15294            whiteTimeRemaining -= lastTickLength;
15295            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15296 //         if(pvInfoList[forwardMostMove].time == -1)
15297                  pvInfoList[forwardMostMove].time =
15298                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15299         }
15300         flagged = CheckFlags();
15301     }
15302     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15303     CheckTimeControl();
15304
15305     if (flagged || !appData.clockMode) return;
15306
15307     switch (gameMode) {
15308       case MachinePlaysBlack:
15309       case MachinePlaysWhite:
15310       case BeginningOfGame:
15311         if (pausing) return;
15312         break;
15313
15314       case EditGame:
15315       case PlayFromGameFile:
15316       case IcsExamining:
15317         return;
15318
15319       default:
15320         break;
15321     }
15322
15323     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15324         if(WhiteOnMove(forwardMostMove))
15325              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15326         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15327     }
15328
15329     tickStartTM = now;
15330     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15331       whiteTimeRemaining : blackTimeRemaining);
15332     StartClockTimer(intendedTickLength);
15333 }
15334
15335
15336 /* Stop both clocks */
15337 void
15338 StopClocks()
15339 {
15340     long lastTickLength;
15341     TimeMark now;
15342
15343     if (!StopClockTimer()) return;
15344     if (!appData.clockMode) return;
15345
15346     GetTimeMark(&now);
15347
15348     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15349     if (WhiteOnMove(forwardMostMove)) {
15350         if(whiteNPS >= 0) lastTickLength = 0;
15351         whiteTimeRemaining -= lastTickLength;
15352         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15353     } else {
15354         if(blackNPS >= 0) lastTickLength = 0;
15355         blackTimeRemaining -= lastTickLength;
15356         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15357     }
15358     CheckFlags();
15359 }
15360
15361 /* Start clock of player on move.  Time may have been reset, so
15362    if clock is already running, stop and restart it. */
15363 void
15364 StartClocks()
15365 {
15366     (void) StopClockTimer(); /* in case it was running already */
15367     DisplayBothClocks();
15368     if (CheckFlags()) return;
15369
15370     if (!appData.clockMode) return;
15371     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15372
15373     GetTimeMark(&tickStartTM);
15374     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15375       whiteTimeRemaining : blackTimeRemaining);
15376
15377    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15378     whiteNPS = blackNPS = -1;
15379     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15380        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15381         whiteNPS = first.nps;
15382     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15383        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15384         blackNPS = first.nps;
15385     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15386         whiteNPS = second.nps;
15387     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15388         blackNPS = second.nps;
15389     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15390
15391     StartClockTimer(intendedTickLength);
15392 }
15393
15394 char *
15395 TimeString(ms)
15396      long ms;
15397 {
15398     long second, minute, hour, day;
15399     char *sign = "";
15400     static char buf[32];
15401
15402     if (ms > 0 && ms <= 9900) {
15403       /* convert milliseconds to tenths, rounding up */
15404       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15405
15406       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15407       return buf;
15408     }
15409
15410     /* convert milliseconds to seconds, rounding up */
15411     /* use floating point to avoid strangeness of integer division
15412        with negative dividends on many machines */
15413     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15414
15415     if (second < 0) {
15416         sign = "-";
15417         second = -second;
15418     }
15419
15420     day = second / (60 * 60 * 24);
15421     second = second % (60 * 60 * 24);
15422     hour = second / (60 * 60);
15423     second = second % (60 * 60);
15424     minute = second / 60;
15425     second = second % 60;
15426
15427     if (day > 0)
15428       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15429               sign, day, hour, minute, second);
15430     else if (hour > 0)
15431       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15432     else
15433       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15434
15435     return buf;
15436 }
15437
15438
15439 /*
15440  * This is necessary because some C libraries aren't ANSI C compliant yet.
15441  */
15442 char *
15443 StrStr(string, match)
15444      char *string, *match;
15445 {
15446     int i, length;
15447
15448     length = strlen(match);
15449
15450     for (i = strlen(string) - length; i >= 0; i--, string++)
15451       if (!strncmp(match, string, length))
15452         return string;
15453
15454     return NULL;
15455 }
15456
15457 char *
15458 StrCaseStr(string, match)
15459      char *string, *match;
15460 {
15461     int i, j, length;
15462
15463     length = strlen(match);
15464
15465     for (i = strlen(string) - length; i >= 0; i--, string++) {
15466         for (j = 0; j < length; j++) {
15467             if (ToLower(match[j]) != ToLower(string[j]))
15468               break;
15469         }
15470         if (j == length) return string;
15471     }
15472
15473     return NULL;
15474 }
15475
15476 #ifndef _amigados
15477 int
15478 StrCaseCmp(s1, s2)
15479      char *s1, *s2;
15480 {
15481     char c1, c2;
15482
15483     for (;;) {
15484         c1 = ToLower(*s1++);
15485         c2 = ToLower(*s2++);
15486         if (c1 > c2) return 1;
15487         if (c1 < c2) return -1;
15488         if (c1 == NULLCHAR) return 0;
15489     }
15490 }
15491
15492
15493 int
15494 ToLower(c)
15495      int c;
15496 {
15497     return isupper(c) ? tolower(c) : c;
15498 }
15499
15500
15501 int
15502 ToUpper(c)
15503      int c;
15504 {
15505     return islower(c) ? toupper(c) : c;
15506 }
15507 #endif /* !_amigados    */
15508
15509 char *
15510 StrSave(s)
15511      char *s;
15512 {
15513   char *ret;
15514
15515   if ((ret = (char *) malloc(strlen(s) + 1)))
15516     {
15517       safeStrCpy(ret, s, strlen(s)+1);
15518     }
15519   return ret;
15520 }
15521
15522 char *
15523 StrSavePtr(s, savePtr)
15524      char *s, **savePtr;
15525 {
15526     if (*savePtr) {
15527         free(*savePtr);
15528     }
15529     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15530       safeStrCpy(*savePtr, s, strlen(s)+1);
15531     }
15532     return(*savePtr);
15533 }
15534
15535 char *
15536 PGNDate()
15537 {
15538     time_t clock;
15539     struct tm *tm;
15540     char buf[MSG_SIZ];
15541
15542     clock = time((time_t *)NULL);
15543     tm = localtime(&clock);
15544     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15545             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15546     return StrSave(buf);
15547 }
15548
15549
15550 char *
15551 PositionToFEN(move, overrideCastling)
15552      int move;
15553      char *overrideCastling;
15554 {
15555     int i, j, fromX, fromY, toX, toY;
15556     int whiteToPlay;
15557     char buf[128];
15558     char *p, *q;
15559     int emptycount;
15560     ChessSquare piece;
15561
15562     whiteToPlay = (gameMode == EditPosition) ?
15563       !blackPlaysFirst : (move % 2 == 0);
15564     p = buf;
15565
15566     /* Piece placement data */
15567     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15568         emptycount = 0;
15569         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15570             if (boards[move][i][j] == EmptySquare) {
15571                 emptycount++;
15572             } else { ChessSquare piece = boards[move][i][j];
15573                 if (emptycount > 0) {
15574                     if(emptycount<10) /* [HGM] can be >= 10 */
15575                         *p++ = '0' + emptycount;
15576                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15577                     emptycount = 0;
15578                 }
15579                 if(PieceToChar(piece) == '+') {
15580                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15581                     *p++ = '+';
15582                     piece = (ChessSquare)(DEMOTED piece);
15583                 }
15584                 *p++ = PieceToChar(piece);
15585                 if(p[-1] == '~') {
15586                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15587                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15588                     *p++ = '~';
15589                 }
15590             }
15591         }
15592         if (emptycount > 0) {
15593             if(emptycount<10) /* [HGM] can be >= 10 */
15594                 *p++ = '0' + emptycount;
15595             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15596             emptycount = 0;
15597         }
15598         *p++ = '/';
15599     }
15600     *(p - 1) = ' ';
15601
15602     /* [HGM] print Crazyhouse or Shogi holdings */
15603     if( gameInfo.holdingsWidth ) {
15604         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15605         q = p;
15606         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15607             piece = boards[move][i][BOARD_WIDTH-1];
15608             if( piece != EmptySquare )
15609               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15610                   *p++ = PieceToChar(piece);
15611         }
15612         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15613             piece = boards[move][BOARD_HEIGHT-i-1][0];
15614             if( piece != EmptySquare )
15615               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15616                   *p++ = PieceToChar(piece);
15617         }
15618
15619         if( q == p ) *p++ = '-';
15620         *p++ = ']';
15621         *p++ = ' ';
15622     }
15623
15624     /* Active color */
15625     *p++ = whiteToPlay ? 'w' : 'b';
15626     *p++ = ' ';
15627
15628   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15629     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15630   } else {
15631   if(nrCastlingRights) {
15632      q = p;
15633      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15634        /* [HGM] write directly from rights */
15635            if(boards[move][CASTLING][2] != NoRights &&
15636               boards[move][CASTLING][0] != NoRights   )
15637                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15638            if(boards[move][CASTLING][2] != NoRights &&
15639               boards[move][CASTLING][1] != NoRights   )
15640                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15641            if(boards[move][CASTLING][5] != NoRights &&
15642               boards[move][CASTLING][3] != NoRights   )
15643                 *p++ = boards[move][CASTLING][3] + AAA;
15644            if(boards[move][CASTLING][5] != NoRights &&
15645               boards[move][CASTLING][4] != NoRights   )
15646                 *p++ = boards[move][CASTLING][4] + AAA;
15647      } else {
15648
15649         /* [HGM] write true castling rights */
15650         if( nrCastlingRights == 6 ) {
15651             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15652                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15653             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15654                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15655             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15656                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15657             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15658                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15659         }
15660      }
15661      if (q == p) *p++ = '-'; /* No castling rights */
15662      *p++ = ' ';
15663   }
15664
15665   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15666      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15667     /* En passant target square */
15668     if (move > backwardMostMove) {
15669         fromX = moveList[move - 1][0] - AAA;
15670         fromY = moveList[move - 1][1] - ONE;
15671         toX = moveList[move - 1][2] - AAA;
15672         toY = moveList[move - 1][3] - ONE;
15673         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15674             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15675             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15676             fromX == toX) {
15677             /* 2-square pawn move just happened */
15678             *p++ = toX + AAA;
15679             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15680         } else {
15681             *p++ = '-';
15682         }
15683     } else if(move == backwardMostMove) {
15684         // [HGM] perhaps we should always do it like this, and forget the above?
15685         if((signed char)boards[move][EP_STATUS] >= 0) {
15686             *p++ = boards[move][EP_STATUS] + AAA;
15687             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15688         } else {
15689             *p++ = '-';
15690         }
15691     } else {
15692         *p++ = '-';
15693     }
15694     *p++ = ' ';
15695   }
15696   }
15697
15698     /* [HGM] find reversible plies */
15699     {   int i = 0, j=move;
15700
15701         if (appData.debugMode) { int k;
15702             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15703             for(k=backwardMostMove; k<=forwardMostMove; k++)
15704                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15705
15706         }
15707
15708         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15709         if( j == backwardMostMove ) i += initialRulePlies;
15710         sprintf(p, "%d ", i);
15711         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15712     }
15713     /* Fullmove number */
15714     sprintf(p, "%d", (move / 2) + 1);
15715
15716     return StrSave(buf);
15717 }
15718
15719 Boolean
15720 ParseFEN(board, blackPlaysFirst, fen)
15721     Board board;
15722      int *blackPlaysFirst;
15723      char *fen;
15724 {
15725     int i, j;
15726     char *p, c;
15727     int emptycount;
15728     ChessSquare piece;
15729
15730     p = fen;
15731
15732     /* [HGM] by default clear Crazyhouse holdings, if present */
15733     if(gameInfo.holdingsWidth) {
15734        for(i=0; i<BOARD_HEIGHT; i++) {
15735            board[i][0]             = EmptySquare; /* black holdings */
15736            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15737            board[i][1]             = (ChessSquare) 0; /* black counts */
15738            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15739        }
15740     }
15741
15742     /* Piece placement data */
15743     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15744         j = 0;
15745         for (;;) {
15746             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15747                 if (*p == '/') p++;
15748                 emptycount = gameInfo.boardWidth - j;
15749                 while (emptycount--)
15750                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15751                 break;
15752 #if(BOARD_FILES >= 10)
15753             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15754                 p++; emptycount=10;
15755                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15756                 while (emptycount--)
15757                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15758 #endif
15759             } else if (isdigit(*p)) {
15760                 emptycount = *p++ - '0';
15761                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15762                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15763                 while (emptycount--)
15764                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15765             } else if (*p == '+' || isalpha(*p)) {
15766                 if (j >= gameInfo.boardWidth) return FALSE;
15767                 if(*p=='+') {
15768                     piece = CharToPiece(*++p);
15769                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15770                     piece = (ChessSquare) (PROMOTED piece ); p++;
15771                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15772                 } else piece = CharToPiece(*p++);
15773
15774                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15775                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15776                     piece = (ChessSquare) (PROMOTED piece);
15777                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15778                     p++;
15779                 }
15780                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15781             } else {
15782                 return FALSE;
15783             }
15784         }
15785     }
15786     while (*p == '/' || *p == ' ') p++;
15787
15788     /* [HGM] look for Crazyhouse holdings here */
15789     while(*p==' ') p++;
15790     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15791         if(*p == '[') p++;
15792         if(*p == '-' ) p++; /* empty holdings */ else {
15793             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15794             /* if we would allow FEN reading to set board size, we would   */
15795             /* have to add holdings and shift the board read so far here   */
15796             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15797                 p++;
15798                 if((int) piece >= (int) BlackPawn ) {
15799                     i = (int)piece - (int)BlackPawn;
15800                     i = PieceToNumber((ChessSquare)i);
15801                     if( i >= gameInfo.holdingsSize ) return FALSE;
15802                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15803                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15804                 } else {
15805                     i = (int)piece - (int)WhitePawn;
15806                     i = PieceToNumber((ChessSquare)i);
15807                     if( i >= gameInfo.holdingsSize ) return FALSE;
15808                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15809                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15810                 }
15811             }
15812         }
15813         if(*p == ']') p++;
15814     }
15815
15816     while(*p == ' ') p++;
15817
15818     /* Active color */
15819     c = *p++;
15820     if(appData.colorNickNames) {
15821       if( c == appData.colorNickNames[0] ) c = 'w'; else
15822       if( c == appData.colorNickNames[1] ) c = 'b';
15823     }
15824     switch (c) {
15825       case 'w':
15826         *blackPlaysFirst = FALSE;
15827         break;
15828       case 'b':
15829         *blackPlaysFirst = TRUE;
15830         break;
15831       default:
15832         return FALSE;
15833     }
15834
15835     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15836     /* return the extra info in global variiables             */
15837
15838     /* set defaults in case FEN is incomplete */
15839     board[EP_STATUS] = EP_UNKNOWN;
15840     for(i=0; i<nrCastlingRights; i++ ) {
15841         board[CASTLING][i] =
15842             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15843     }   /* assume possible unless obviously impossible */
15844     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15845     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15846     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15847                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15848     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15849     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15850     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15851                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15852     FENrulePlies = 0;
15853
15854     while(*p==' ') p++;
15855     if(nrCastlingRights) {
15856       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15857           /* castling indicator present, so default becomes no castlings */
15858           for(i=0; i<nrCastlingRights; i++ ) {
15859                  board[CASTLING][i] = NoRights;
15860           }
15861       }
15862       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15863              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15864              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15865              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15866         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15867
15868         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15869             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15870             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15871         }
15872         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15873             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15874         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15875                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15876         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15877                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15878         switch(c) {
15879           case'K':
15880               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15881               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15882               board[CASTLING][2] = whiteKingFile;
15883               break;
15884           case'Q':
15885               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15886               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15887               board[CASTLING][2] = whiteKingFile;
15888               break;
15889           case'k':
15890               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15891               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15892               board[CASTLING][5] = blackKingFile;
15893               break;
15894           case'q':
15895               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15896               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15897               board[CASTLING][5] = blackKingFile;
15898           case '-':
15899               break;
15900           default: /* FRC castlings */
15901               if(c >= 'a') { /* black rights */
15902                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15903                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15904                   if(i == BOARD_RGHT) break;
15905                   board[CASTLING][5] = i;
15906                   c -= AAA;
15907                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15908                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15909                   if(c > i)
15910                       board[CASTLING][3] = c;
15911                   else
15912                       board[CASTLING][4] = c;
15913               } else { /* white rights */
15914                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15915                     if(board[0][i] == WhiteKing) break;
15916                   if(i == BOARD_RGHT) break;
15917                   board[CASTLING][2] = i;
15918                   c -= AAA - 'a' + 'A';
15919                   if(board[0][c] >= WhiteKing) break;
15920                   if(c > i)
15921                       board[CASTLING][0] = c;
15922                   else
15923                       board[CASTLING][1] = c;
15924               }
15925         }
15926       }
15927       for(i=0; i<nrCastlingRights; i++)
15928         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15929     if (appData.debugMode) {
15930         fprintf(debugFP, "FEN castling rights:");
15931         for(i=0; i<nrCastlingRights; i++)
15932         fprintf(debugFP, " %d", board[CASTLING][i]);
15933         fprintf(debugFP, "\n");
15934     }
15935
15936       while(*p==' ') p++;
15937     }
15938
15939     /* read e.p. field in games that know e.p. capture */
15940     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15941        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15942       if(*p=='-') {
15943         p++; board[EP_STATUS] = EP_NONE;
15944       } else {
15945          char c = *p++ - AAA;
15946
15947          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15948          if(*p >= '0' && *p <='9') p++;
15949          board[EP_STATUS] = c;
15950       }
15951     }
15952
15953
15954     if(sscanf(p, "%d", &i) == 1) {
15955         FENrulePlies = i; /* 50-move ply counter */
15956         /* (The move number is still ignored)    */
15957     }
15958
15959     return TRUE;
15960 }
15961
15962 void
15963 EditPositionPasteFEN(char *fen)
15964 {
15965   if (fen != NULL) {
15966     Board initial_position;
15967
15968     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15969       DisplayError(_("Bad FEN position in clipboard"), 0);
15970       return ;
15971     } else {
15972       int savedBlackPlaysFirst = blackPlaysFirst;
15973       EditPositionEvent();
15974       blackPlaysFirst = savedBlackPlaysFirst;
15975       CopyBoard(boards[0], initial_position);
15976       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15977       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15978       DisplayBothClocks();
15979       DrawPosition(FALSE, boards[currentMove]);
15980     }
15981   }
15982 }
15983
15984 static char cseq[12] = "\\   ";
15985
15986 Boolean set_cont_sequence(char *new_seq)
15987 {
15988     int len;
15989     Boolean ret;
15990
15991     // handle bad attempts to set the sequence
15992         if (!new_seq)
15993                 return 0; // acceptable error - no debug
15994
15995     len = strlen(new_seq);
15996     ret = (len > 0) && (len < sizeof(cseq));
15997     if (ret)
15998       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15999     else if (appData.debugMode)
16000       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16001     return ret;
16002 }
16003
16004 /*
16005     reformat a source message so words don't cross the width boundary.  internal
16006     newlines are not removed.  returns the wrapped size (no null character unless
16007     included in source message).  If dest is NULL, only calculate the size required
16008     for the dest buffer.  lp argument indicats line position upon entry, and it's
16009     passed back upon exit.
16010 */
16011 int wrap(char *dest, char *src, int count, int width, int *lp)
16012 {
16013     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16014
16015     cseq_len = strlen(cseq);
16016     old_line = line = *lp;
16017     ansi = len = clen = 0;
16018
16019     for (i=0; i < count; i++)
16020     {
16021         if (src[i] == '\033')
16022             ansi = 1;
16023
16024         // if we hit the width, back up
16025         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16026         {
16027             // store i & len in case the word is too long
16028             old_i = i, old_len = len;
16029
16030             // find the end of the last word
16031             while (i && src[i] != ' ' && src[i] != '\n')
16032             {
16033                 i--;
16034                 len--;
16035             }
16036
16037             // word too long?  restore i & len before splitting it
16038             if ((old_i-i+clen) >= width)
16039             {
16040                 i = old_i;
16041                 len = old_len;
16042             }
16043
16044             // extra space?
16045             if (i && src[i-1] == ' ')
16046                 len--;
16047
16048             if (src[i] != ' ' && src[i] != '\n')
16049             {
16050                 i--;
16051                 if (len)
16052                     len--;
16053             }
16054
16055             // now append the newline and continuation sequence
16056             if (dest)
16057                 dest[len] = '\n';
16058             len++;
16059             if (dest)
16060                 strncpy(dest+len, cseq, cseq_len);
16061             len += cseq_len;
16062             line = cseq_len;
16063             clen = cseq_len;
16064             continue;
16065         }
16066
16067         if (dest)
16068             dest[len] = src[i];
16069         len++;
16070         if (!ansi)
16071             line++;
16072         if (src[i] == '\n')
16073             line = 0;
16074         if (src[i] == 'm')
16075             ansi = 0;
16076     }
16077     if (dest && appData.debugMode)
16078     {
16079         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16080             count, width, line, len, *lp);
16081         show_bytes(debugFP, src, count);
16082         fprintf(debugFP, "\ndest: ");
16083         show_bytes(debugFP, dest, len);
16084         fprintf(debugFP, "\n");
16085     }
16086     *lp = dest ? line : old_line;
16087
16088     return len;
16089 }
16090
16091 // [HGM] vari: routines for shelving variations
16092
16093 void
16094 PushInner(int firstMove, int lastMove)
16095 {
16096         int i, j, nrMoves = lastMove - firstMove;
16097
16098         // push current tail of game on stack
16099         savedResult[storedGames] = gameInfo.result;
16100         savedDetails[storedGames] = gameInfo.resultDetails;
16101         gameInfo.resultDetails = NULL;
16102         savedFirst[storedGames] = firstMove;
16103         savedLast [storedGames] = lastMove;
16104         savedFramePtr[storedGames] = framePtr;
16105         framePtr -= nrMoves; // reserve space for the boards
16106         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16107             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16108             for(j=0; j<MOVE_LEN; j++)
16109                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16110             for(j=0; j<2*MOVE_LEN; j++)
16111                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16112             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16113             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16114             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16115             pvInfoList[firstMove+i-1].depth = 0;
16116             commentList[framePtr+i] = commentList[firstMove+i];
16117             commentList[firstMove+i] = NULL;
16118         }
16119
16120         storedGames++;
16121         forwardMostMove = firstMove; // truncate game so we can start variation
16122 }
16123
16124 void
16125 PushTail(int firstMove, int lastMove)
16126 {
16127         if(appData.icsActive) { // only in local mode
16128                 forwardMostMove = currentMove; // mimic old ICS behavior
16129                 return;
16130         }
16131         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16132
16133         PushInner(firstMove, lastMove);
16134         if(storedGames == 1) GreyRevert(FALSE);
16135 }
16136
16137 void
16138 PopInner(Boolean annotate)
16139 {
16140         int i, j, nrMoves;
16141         char buf[8000], moveBuf[20];
16142
16143         storedGames--;
16144         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16145         nrMoves = savedLast[storedGames] - currentMove;
16146         if(annotate) {
16147                 int cnt = 10;
16148                 if(!WhiteOnMove(currentMove))
16149                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16150                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16151                 for(i=currentMove; i<forwardMostMove; i++) {
16152                         if(WhiteOnMove(i))
16153                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16154                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16155                         strcat(buf, moveBuf);
16156                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16157                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16158                 }
16159                 strcat(buf, ")");
16160         }
16161         for(i=1; i<=nrMoves; i++) { // copy last variation back
16162             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16163             for(j=0; j<MOVE_LEN; j++)
16164                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16165             for(j=0; j<2*MOVE_LEN; j++)
16166                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16167             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16168             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16169             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16170             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16171             commentList[currentMove+i] = commentList[framePtr+i];
16172             commentList[framePtr+i] = NULL;
16173         }
16174         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16175         framePtr = savedFramePtr[storedGames];
16176         gameInfo.result = savedResult[storedGames];
16177         if(gameInfo.resultDetails != NULL) {
16178             free(gameInfo.resultDetails);
16179       }
16180         gameInfo.resultDetails = savedDetails[storedGames];
16181         forwardMostMove = currentMove + nrMoves;
16182 }
16183
16184 Boolean
16185 PopTail(Boolean annotate)
16186 {
16187         if(appData.icsActive) return FALSE; // only in local mode
16188         if(!storedGames) return FALSE; // sanity
16189         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16190
16191         PopInner(annotate);
16192
16193         if(storedGames == 0) GreyRevert(TRUE);
16194         return TRUE;
16195 }
16196
16197 void
16198 CleanupTail()
16199 {       // remove all shelved variations
16200         int i;
16201         for(i=0; i<storedGames; i++) {
16202             if(savedDetails[i])
16203                 free(savedDetails[i]);
16204             savedDetails[i] = NULL;
16205         }
16206         for(i=framePtr; i<MAX_MOVES; i++) {
16207                 if(commentList[i]) free(commentList[i]);
16208                 commentList[i] = NULL;
16209         }
16210         framePtr = MAX_MOVES-1;
16211         storedGames = 0;
16212 }
16213
16214 void
16215 LoadVariation(int index, char *text)
16216 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16217         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16218         int level = 0, move;
16219
16220         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16221         // first find outermost bracketing variation
16222         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16223             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16224                 if(*p == '{') wait = '}'; else
16225                 if(*p == '[') wait = ']'; else
16226                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16227                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16228             }
16229             if(*p == wait) wait = NULLCHAR; // closing ]} found
16230             p++;
16231         }
16232         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16233         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16234         end[1] = NULLCHAR; // clip off comment beyond variation
16235         ToNrEvent(currentMove-1);
16236         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16237         // kludge: use ParsePV() to append variation to game
16238         move = currentMove;
16239         ParsePV(start, TRUE);
16240         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16241         ClearPremoveHighlights();
16242         CommentPopDown();
16243         ToNrEvent(currentMove+1);
16244 }
16245