51f348e1da46af00d4e0cbb804dbed5551c09784
[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 FILE *WriteTourneyFile P((char *results));
237
238 #ifdef WIN32
239        extern void ConsoleCreate();
240 #endif
241
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
245
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 Boolean abortMatch;
254
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 int endPV = -1;
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
262 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
266 Boolean partnerUp;
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
278 int chattingPartner;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
284
285 /* States for ics_getting_history */
286 #define H_FALSE 0
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
292
293 /* whosays values for GameEnds */
294 #define GE_ICS 0
295 #define GE_ENGINE 1
296 #define GE_PLAYER 2
297 #define GE_FILE 3
298 #define GE_XBOARD 4
299 #define GE_ENGINE1 5
300 #define GE_ENGINE2 6
301
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
304
305 /* Different types of move when calling RegisterMove */
306 #define CMAIL_MOVE   0
307 #define CMAIL_RESIGN 1
308 #define CMAIL_DRAW   2
309 #define CMAIL_ACCEPT 3
310
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
315
316 /* Telnet protocol constants */
317 #define TN_WILL 0373
318 #define TN_WONT 0374
319 #define TN_DO   0375
320 #define TN_DONT 0376
321 #define TN_IAC  0377
322 #define TN_ECHO 0001
323 #define TN_SGA  0003
324 #define TN_PORT 23
325
326 char*
327 safeStrCpy( char *dst, const char *src, size_t count )
328 { // [HGM] made safe
329   int i;
330   assert( dst != NULL );
331   assert( src != NULL );
332   assert( count > 0 );
333
334   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335   if(  i == count && dst[count-1] != NULLCHAR)
336     {
337       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338       if(appData.debugMode)
339       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340     }
341
342   return dst;
343 }
344
345 /* Some compiler can't cast u64 to double
346  * This function do the job for us:
347
348  * We use the highest bit for cast, this only
349  * works if the highest bit is not
350  * in use (This should not happen)
351  *
352  * We used this for all compiler
353  */
354 double
355 u64ToDouble(u64 value)
356 {
357   double r;
358   u64 tmp = value & u64Const(0x7fffffffffffffff);
359   r = (double)(s64)tmp;
360   if (value & u64Const(0x8000000000000000))
361        r +=  9.2233720368547758080e18; /* 2^63 */
362  return r;
363 }
364
365 /* Fake up flags for now, as we aren't keeping track of castling
366    availability yet. [HGM] Change of logic: the flag now only
367    indicates the type of castlings allowed by the rule of the game.
368    The actual rights themselves are maintained in the array
369    castlingRights, as part of the game history, and are not probed
370    by this function.
371  */
372 int
373 PosFlags(index)
374 {
375   int flags = F_ALL_CASTLE_OK;
376   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377   switch (gameInfo.variant) {
378   case VariantSuicide:
379     flags &= ~F_ALL_CASTLE_OK;
380   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381     flags |= F_IGNORE_CHECK;
382   case VariantLosers:
383     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384     break;
385   case VariantAtomic:
386     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387     break;
388   case VariantKriegspiel:
389     flags |= F_KRIEGSPIEL_CAPTURE;
390     break;
391   case VariantCapaRandom:
392   case VariantFischeRandom:
393     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394   case VariantNoCastle:
395   case VariantShatranj:
396   case VariantCourier:
397   case VariantMakruk:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     if(n) return; // only startup first engine immediately; second can wait
865     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
866     LoadEngine();
867 }
868
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871
872 static char resetOptions[] = 
873         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
880     if(engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         q = firstChessProgramNames;
912         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
914                         useNick ? " -fn \"" : "",
915                         useNick ? nickName : "",
916                         useNick ? "\"" : "",
917                         v1 ? " -firstProtocolVersion 1" : "",
918                         hasBook ? "" : " -fNoOwnBookUCI",
919                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920                         storeVariant ? " -variant " : "",
921                         storeVariant ? VariantName(gameInfo.variant) : "");
922         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
924         if(q)   free(q);
925     }
926     ReplaceEngine(cps, i);
927 }
928
929 void
930 InitTimeControls()
931 {
932     int matched, min, sec;
933     /*
934      * Parse timeControl resource
935      */
936     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937                           appData.movesPerSession)) {
938         char buf[MSG_SIZ];
939         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940         DisplayFatalError(buf, 0, 2);
941     }
942
943     /*
944      * Parse searchTime resource
945      */
946     if (*appData.searchTime != NULLCHAR) {
947         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
948         if (matched == 1) {
949             searchTime = min * 60;
950         } else if (matched == 2) {
951             searchTime = min * 60 + sec;
952         } else {
953             char buf[MSG_SIZ];
954             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955             DisplayFatalError(buf, 0, 2);
956         }
957     }
958 }
959
960 void
961 InitBackEnd1()
962 {
963
964     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
966
967     GetTimeMark(&programStartTime);
968     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
970
971     ClearProgramStats();
972     programStats.ok_to_send = 1;
973     programStats.seen_stat = 0;
974
975     /*
976      * Initialize game list
977      */
978     ListNew(&gameList);
979
980
981     /*
982      * Internet chess server status
983      */
984     if (appData.icsActive) {
985         appData.matchMode = FALSE;
986         appData.matchGames = 0;
987 #if ZIPPY
988         appData.noChessProgram = !appData.zippyPlay;
989 #else
990         appData.zippyPlay = FALSE;
991         appData.zippyTalk = FALSE;
992         appData.noChessProgram = TRUE;
993 #endif
994         if (*appData.icsHelper != NULLCHAR) {
995             appData.useTelnet = TRUE;
996             appData.telnetProgram = appData.icsHelper;
997         }
998     } else {
999         appData.zippyTalk = appData.zippyPlay = FALSE;
1000     }
1001
1002     /* [AS] Initialize pv info list [HGM] and game state */
1003     {
1004         int i, j;
1005
1006         for( i=0; i<=framePtr; i++ ) {
1007             pvInfoList[i].depth = -1;
1008             boards[i][EP_STATUS] = EP_NONE;
1009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1010         }
1011     }
1012
1013     InitTimeControls();
1014
1015     /* [AS] Adjudication threshold */
1016     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1017
1018     InitEngine(&first, 0);
1019     InitEngine(&second, 1);
1020     CommonEngineInit();
1021
1022     pairing.which = "pairing"; // pairing engine
1023     pairing.pr = NoProc;
1024     pairing.isr = NULL;
1025     pairing.program = appData.pairingEngine;
1026     pairing.host = "localhost";
1027     pairing.dir = ".";
1028
1029     if (appData.icsActive) {
1030         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1031     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1032         appData.clockMode = FALSE;
1033         first.sendTime = second.sendTime = 0;
1034     }
1035
1036 #if ZIPPY
1037     /* Override some settings from environment variables, for backward
1038        compatibility.  Unfortunately it's not feasible to have the env
1039        vars just set defaults, at least in xboard.  Ugh.
1040     */
1041     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1042       ZippyInit();
1043     }
1044 #endif
1045
1046     if (!appData.icsActive) {
1047       char buf[MSG_SIZ];
1048       int len;
1049
1050       /* Check for variants that are supported only in ICS mode,
1051          or not at all.  Some that are accepted here nevertheless
1052          have bugs; see comments below.
1053       */
1054       VariantClass variant = StringToVariant(appData.variant);
1055       switch (variant) {
1056       case VariantBughouse:     /* need four players and two boards */
1057       case VariantKriegspiel:   /* need to hide pieces and move details */
1058         /* case VariantFischeRandom: (Fabien: moved below) */
1059         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1060         if( (len > MSG_SIZ) && appData.debugMode )
1061           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1062
1063         DisplayFatalError(buf, 0, 2);
1064         return;
1065
1066       case VariantUnknown:
1067       case VariantLoadable:
1068       case Variant29:
1069       case Variant30:
1070       case Variant31:
1071       case Variant32:
1072       case Variant33:
1073       case Variant34:
1074       case Variant35:
1075       case Variant36:
1076       default:
1077         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1078         if( (len > MSG_SIZ) && appData.debugMode )
1079           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1080
1081         DisplayFatalError(buf, 0, 2);
1082         return;
1083
1084       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1085       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1086       case VariantGothic:     /* [HGM] should work */
1087       case VariantCapablanca: /* [HGM] should work */
1088       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1089       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1090       case VariantKnightmate: /* [HGM] should work */
1091       case VariantCylinder:   /* [HGM] untested */
1092       case VariantFalcon:     /* [HGM] untested */
1093       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1094                                  offboard interposition not understood */
1095       case VariantNormal:     /* definitely works! */
1096       case VariantWildCastle: /* pieces not automatically shuffled */
1097       case VariantNoCastle:   /* pieces not automatically shuffled */
1098       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1099       case VariantLosers:     /* should work except for win condition,
1100                                  and doesn't know captures are mandatory */
1101       case VariantSuicide:    /* should work except for win condition,
1102                                  and doesn't know captures are mandatory */
1103       case VariantGiveaway:   /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantTwoKings:   /* should work */
1106       case VariantAtomic:     /* should work except for win condition */
1107       case Variant3Check:     /* should work except for win condition */
1108       case VariantShatranj:   /* should work except for all win conditions */
1109       case VariantMakruk:     /* should work except for daw countdown */
1110       case VariantBerolina:   /* might work if TestLegality is off */
1111       case VariantCapaRandom: /* should work */
1112       case VariantJanus:      /* should work */
1113       case VariantSuper:      /* experimental */
1114       case VariantGreat:      /* experimental, requires legality testing to be off */
1115       case VariantSChess:     /* S-Chess, should work */
1116       case VariantSpartan:    /* should work */
1117         break;
1118       }
1119     }
1120
1121 }
1122
1123 int NextIntegerFromString( char ** str, long * value )
1124 {
1125     int result = -1;
1126     char * s = *str;
1127
1128     while( *s == ' ' || *s == '\t' ) {
1129         s++;
1130     }
1131
1132     *value = 0;
1133
1134     if( *s >= '0' && *s <= '9' ) {
1135         while( *s >= '0' && *s <= '9' ) {
1136             *value = *value * 10 + (*s - '0');
1137             s++;
1138         }
1139
1140         result = 0;
1141     }
1142
1143     *str = s;
1144
1145     return result;
1146 }
1147
1148 int NextTimeControlFromString( char ** str, long * value )
1149 {
1150     long temp;
1151     int result = NextIntegerFromString( str, &temp );
1152
1153     if( result == 0 ) {
1154         *value = temp * 60; /* Minutes */
1155         if( **str == ':' ) {
1156             (*str)++;
1157             result = NextIntegerFromString( str, &temp );
1158             *value += temp; /* Seconds */
1159         }
1160     }
1161
1162     return result;
1163 }
1164
1165 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1166 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1167     int result = -1, type = 0; long temp, temp2;
1168
1169     if(**str != ':') return -1; // old params remain in force!
1170     (*str)++;
1171     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1172     if( NextIntegerFromString( str, &temp ) ) return -1;
1173     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1174
1175     if(**str != '/') {
1176         /* time only: incremental or sudden-death time control */
1177         if(**str == '+') { /* increment follows; read it */
1178             (*str)++;
1179             if(**str == '!') type = *(*str)++; // Bronstein TC
1180             if(result = NextIntegerFromString( str, &temp2)) return -1;
1181             *inc = temp2 * 1000;
1182             if(**str == '.') { // read fraction of increment
1183                 char *start = ++(*str);
1184                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1185                 temp2 *= 1000;
1186                 while(start++ < *str) temp2 /= 10;
1187                 *inc += temp2;
1188             }
1189         } else *inc = 0;
1190         *moves = 0; *tc = temp * 1000; *incType = type;
1191         return 0;
1192     }
1193
1194     (*str)++; /* classical time control */
1195     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1196
1197     if(result == 0) {
1198         *moves = temp;
1199         *tc    = temp2 * 1000;
1200         *inc   = 0;
1201         *incType = type;
1202     }
1203     return result;
1204 }
1205
1206 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1207 {   /* [HGM] get time to add from the multi-session time-control string */
1208     int incType, moves=1; /* kludge to force reading of first session */
1209     long time, increment;
1210     char *s = tcString;
1211
1212     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1213     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1214     do {
1215         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1216         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1217         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1218         if(movenr == -1) return time;    /* last move before new session     */
1219         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1220         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1221         if(!moves) return increment;     /* current session is incremental   */
1222         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1223     } while(movenr >= -1);               /* try again for next session       */
1224
1225     return 0; // no new time quota on this move
1226 }
1227
1228 int
1229 ParseTimeControl(tc, ti, mps)
1230      char *tc;
1231      float ti;
1232      int mps;
1233 {
1234   long tc1;
1235   long tc2;
1236   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1237   int min, sec=0;
1238
1239   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1240   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1241       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1242   if(ti > 0) {
1243
1244     if(mps)
1245       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1246     else 
1247       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1248   } else {
1249     if(mps)
1250       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1251     else 
1252       snprintf(buf, MSG_SIZ, ":%s", mytc);
1253   }
1254   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1255   
1256   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1257     return FALSE;
1258   }
1259
1260   if( *tc == '/' ) {
1261     /* Parse second time control */
1262     tc++;
1263
1264     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1265       return FALSE;
1266     }
1267
1268     if( tc2 == 0 ) {
1269       return FALSE;
1270     }
1271
1272     timeControl_2 = tc2 * 1000;
1273   }
1274   else {
1275     timeControl_2 = 0;
1276   }
1277
1278   if( tc1 == 0 ) {
1279     return FALSE;
1280   }
1281
1282   timeControl = tc1 * 1000;
1283
1284   if (ti >= 0) {
1285     timeIncrement = ti * 1000;  /* convert to ms */
1286     movesPerSession = 0;
1287   } else {
1288     timeIncrement = 0;
1289     movesPerSession = mps;
1290   }
1291   return TRUE;
1292 }
1293
1294 void
1295 InitBackEnd2()
1296 {
1297     if (appData.debugMode) {
1298         fprintf(debugFP, "%s\n", programVersion);
1299     }
1300
1301     set_cont_sequence(appData.wrapContSeq);
1302     if (appData.matchGames > 0) {
1303         appData.matchMode = TRUE;
1304     } else if (appData.matchMode) {
1305         appData.matchGames = 1;
1306     }
1307     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1308         appData.matchGames = appData.sameColorGames;
1309     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1310         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1311         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1312     }
1313     Reset(TRUE, FALSE);
1314     if (appData.noChessProgram || first.protocolVersion == 1) {
1315       InitBackEnd3();
1316     } else {
1317       /* kludge: allow timeout for initial "feature" commands */
1318       FreezeUI();
1319       DisplayMessage("", _("Starting chess program"));
1320       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1321     }
1322 }
1323
1324 int
1325 CalculateIndex(int index, int gameNr)
1326 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1327     int res;
1328     if(index > 0) return index; // fixed nmber
1329     if(index == 0) return 1;
1330     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1331     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1332     return res;
1333 }
1334
1335 int
1336 LoadGameOrPosition(int gameNr)
1337 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1338     if (*appData.loadGameFile != NULLCHAR) {
1339         if (!LoadGameFromFile(appData.loadGameFile,
1340                 CalculateIndex(appData.loadGameIndex, gameNr),
1341                               appData.loadGameFile, FALSE)) {
1342             DisplayFatalError(_("Bad game file"), 0, 1);
1343             return 0;
1344         }
1345     } else if (*appData.loadPositionFile != NULLCHAR) {
1346         if (!LoadPositionFromFile(appData.loadPositionFile,
1347                 CalculateIndex(appData.loadPositionIndex, gameNr),
1348                                   appData.loadPositionFile)) {
1349             DisplayFatalError(_("Bad position file"), 0, 1);
1350             return 0;
1351         }
1352     }
1353     return 1;
1354 }
1355
1356 void
1357 ReserveGame(int gameNr, char resChar)
1358 {
1359     FILE *tf = fopen(appData.tourneyFile, "r+");
1360     char *p, *q, c, buf[MSG_SIZ];
1361     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1362     safeStrCpy(buf, lastMsg, MSG_SIZ);
1363     DisplayMessage(_("Pick new game"), "");
1364     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1365     ParseArgsFromFile(tf);
1366     p = q = appData.results;
1367     if(appData.debugMode) {
1368       char *r = appData.participants;
1369       fprintf(debugFP, "results = '%s'\n", p);
1370       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1371       fprintf(debugFP, "\n");
1372     }
1373     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1374     nextGame = q - p;
1375     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1376     safeStrCpy(q, p, strlen(p) + 2);
1377     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1378     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1379     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1380         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1381         q[nextGame] = '*';
1382     }
1383     fseek(tf, -(strlen(p)+4), SEEK_END);
1384     c = fgetc(tf);
1385     if(c != '"') // depending on DOS or Unix line endings we can be one off
1386          fseek(tf, -(strlen(p)+2), SEEK_END);
1387     else fseek(tf, -(strlen(p)+3), SEEK_END);
1388     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1389     DisplayMessage(buf, "");
1390     free(p); appData.results = q;
1391     if(nextGame <= appData.matchGames && resChar != ' ' &&
1392        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1393         UnloadEngine(&first);  // next game belongs to other pairing;
1394         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1395     }
1396 }
1397
1398 void
1399 MatchEvent(int mode)
1400 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1401         int dummy;
1402         if(matchMode) { // already in match mode: switch it off
1403             abortMatch = TRUE;
1404             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1405             ModeHighlight(); // kludgey way to remove checkmark...
1406             return;
1407         }
1408 //      if(gameMode != BeginningOfGame) {
1409 //          DisplayError(_("You can only start a match from the initial position."), 0);
1410 //          return;
1411 //      }
1412         abortMatch = FALSE;
1413         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1414         /* Set up machine vs. machine match */
1415         nextGame = 0;
1416         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1417         if(appData.tourneyFile[0]) {
1418             ReserveGame(-1, 0);
1419             if(nextGame > appData.matchGames) {
1420                 char buf[MSG_SIZ];
1421                 if(strchr(appData.results, '*') == NULL) {
1422                     FILE *f;
1423                     appData.tourneyCycles++;
1424                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1425                         fclose(f);
1426                         NextTourneyGame(-1, &dummy);
1427                         ReserveGame(-1, 0);
1428                         if(nextGame <= appData.matchGames) {
1429                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1430                             matchMode = mode;
1431                             ScheduleDelayedEvent(NextMatchGame, 10000);
1432                             return;
1433                         }
1434                     }
1435                 }
1436                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1437                 DisplayError(buf, 0);
1438                 appData.tourneyFile[0] = 0;
1439                 return;
1440             }
1441         } else
1442         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1443             DisplayFatalError(_("Can't have a match with no chess programs"),
1444                               0, 2);
1445             return;
1446         }
1447         matchMode = mode;
1448         matchGame = roundNr = 1;
1449         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1450         NextMatchGame();
1451 }
1452
1453 void
1454 InitBackEnd3 P((void))
1455 {
1456     GameMode initialMode;
1457     char buf[MSG_SIZ];
1458     int err, len;
1459
1460     InitChessProgram(&first, startedFromSetupPosition);
1461
1462     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1463         free(programVersion);
1464         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1465         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1466     }
1467
1468     if (appData.icsActive) {
1469 #ifdef WIN32
1470         /* [DM] Make a console window if needed [HGM] merged ifs */
1471         ConsoleCreate();
1472 #endif
1473         err = establish();
1474         if (err != 0)
1475           {
1476             if (*appData.icsCommPort != NULLCHAR)
1477               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1478                              appData.icsCommPort);
1479             else
1480               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1481                         appData.icsHost, appData.icsPort);
1482
1483             if( (len > MSG_SIZ) && appData.debugMode )
1484               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1485
1486             DisplayFatalError(buf, err, 1);
1487             return;
1488         }
1489         SetICSMode();
1490         telnetISR =
1491           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1492         fromUserISR =
1493           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1494         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1495             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1496     } else if (appData.noChessProgram) {
1497         SetNCPMode();
1498     } else {
1499         SetGNUMode();
1500     }
1501
1502     if (*appData.cmailGameName != NULLCHAR) {
1503         SetCmailMode();
1504         OpenLoopback(&cmailPR);
1505         cmailISR =
1506           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1507     }
1508
1509     ThawUI();
1510     DisplayMessage("", "");
1511     if (StrCaseCmp(appData.initialMode, "") == 0) {
1512       initialMode = BeginningOfGame;
1513       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1514         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1515         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1516         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1517         ModeHighlight();
1518       }
1519     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1520       initialMode = TwoMachinesPlay;
1521     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1522       initialMode = AnalyzeFile;
1523     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1524       initialMode = AnalyzeMode;
1525     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1526       initialMode = MachinePlaysWhite;
1527     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1528       initialMode = MachinePlaysBlack;
1529     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1530       initialMode = EditGame;
1531     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1532       initialMode = EditPosition;
1533     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1534       initialMode = Training;
1535     } else {
1536       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1537       if( (len > MSG_SIZ) && appData.debugMode )
1538         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1539
1540       DisplayFatalError(buf, 0, 2);
1541       return;
1542     }
1543
1544     if (appData.matchMode) {
1545         if(appData.tourneyFile[0]) { // start tourney from command line
1546             FILE *f;
1547             if(f = fopen(appData.tourneyFile, "r")) {
1548                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1549                 fclose(f);
1550             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1551         }
1552         MatchEvent(TRUE);
1553     } else if (*appData.cmailGameName != NULLCHAR) {
1554         /* Set up cmail mode */
1555         ReloadCmailMsgEvent(TRUE);
1556     } else {
1557         /* Set up other modes */
1558         if (initialMode == AnalyzeFile) {
1559           if (*appData.loadGameFile == NULLCHAR) {
1560             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1561             return;
1562           }
1563         }
1564         if (*appData.loadGameFile != NULLCHAR) {
1565             (void) LoadGameFromFile(appData.loadGameFile,
1566                                     appData.loadGameIndex,
1567                                     appData.loadGameFile, TRUE);
1568         } else if (*appData.loadPositionFile != NULLCHAR) {
1569             (void) LoadPositionFromFile(appData.loadPositionFile,
1570                                         appData.loadPositionIndex,
1571                                         appData.loadPositionFile);
1572             /* [HGM] try to make self-starting even after FEN load */
1573             /* to allow automatic setup of fairy variants with wtm */
1574             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1575                 gameMode = BeginningOfGame;
1576                 setboardSpoiledMachineBlack = 1;
1577             }
1578             /* [HGM] loadPos: make that every new game uses the setup */
1579             /* from file as long as we do not switch variant          */
1580             if(!blackPlaysFirst) {
1581                 startedFromPositionFile = TRUE;
1582                 CopyBoard(filePosition, boards[0]);
1583             }
1584         }
1585         if (initialMode == AnalyzeMode) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1588             return;
1589           }
1590           if (appData.icsActive) {
1591             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1592             return;
1593           }
1594           AnalyzeModeEvent();
1595         } else if (initialMode == AnalyzeFile) {
1596           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1597           ShowThinkingEvent();
1598           AnalyzeFileEvent();
1599           AnalysisPeriodicEvent(1);
1600         } else if (initialMode == MachinePlaysWhite) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1603                               0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1608                               0, 2);
1609             return;
1610           }
1611           MachineWhiteEvent();
1612         } else if (initialMode == MachinePlaysBlack) {
1613           if (appData.noChessProgram) {
1614             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1615                               0, 2);
1616             return;
1617           }
1618           if (appData.icsActive) {
1619             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1620                               0, 2);
1621             return;
1622           }
1623           MachineBlackEvent();
1624         } else if (initialMode == TwoMachinesPlay) {
1625           if (appData.noChessProgram) {
1626             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1627                               0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1632                               0, 2);
1633             return;
1634           }
1635           TwoMachinesEvent();
1636         } else if (initialMode == EditGame) {
1637           EditGameEvent();
1638         } else if (initialMode == EditPosition) {
1639           EditPositionEvent();
1640         } else if (initialMode == Training) {
1641           if (*appData.loadGameFile == NULLCHAR) {
1642             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1643             return;
1644           }
1645           TrainingEvent();
1646         }
1647     }
1648 }
1649
1650 /*
1651  * Establish will establish a contact to a remote host.port.
1652  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1653  *  used to talk to the host.
1654  * Returns 0 if okay, error code if not.
1655  */
1656 int
1657 establish()
1658 {
1659     char buf[MSG_SIZ];
1660
1661     if (*appData.icsCommPort != NULLCHAR) {
1662         /* Talk to the host through a serial comm port */
1663         return OpenCommPort(appData.icsCommPort, &icsPR);
1664
1665     } else if (*appData.gateway != NULLCHAR) {
1666         if (*appData.remoteShell == NULLCHAR) {
1667             /* Use the rcmd protocol to run telnet program on a gateway host */
1668             snprintf(buf, sizeof(buf), "%s %s %s",
1669                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1670             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1671
1672         } else {
1673             /* Use the rsh program to run telnet program on a gateway host */
1674             if (*appData.remoteUser == NULLCHAR) {
1675                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1676                         appData.gateway, appData.telnetProgram,
1677                         appData.icsHost, appData.icsPort);
1678             } else {
1679                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1680                         appData.remoteShell, appData.gateway,
1681                         appData.remoteUser, appData.telnetProgram,
1682                         appData.icsHost, appData.icsPort);
1683             }
1684             return StartChildProcess(buf, "", &icsPR);
1685
1686         }
1687     } else if (appData.useTelnet) {
1688         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1689
1690     } else {
1691         /* TCP socket interface differs somewhat between
1692            Unix and NT; handle details in the front end.
1693            */
1694         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1695     }
1696 }
1697
1698 void EscapeExpand(char *p, char *q)
1699 {       // [HGM] initstring: routine to shape up string arguments
1700         while(*p++ = *q++) if(p[-1] == '\\')
1701             switch(*q++) {
1702                 case 'n': p[-1] = '\n'; break;
1703                 case 'r': p[-1] = '\r'; break;
1704                 case 't': p[-1] = '\t'; break;
1705                 case '\\': p[-1] = '\\'; break;
1706                 case 0: *p = 0; return;
1707                 default: p[-1] = q[-1]; break;
1708             }
1709 }
1710
1711 void
1712 show_bytes(fp, buf, count)
1713      FILE *fp;
1714      char *buf;
1715      int count;
1716 {
1717     while (count--) {
1718         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1719             fprintf(fp, "\\%03o", *buf & 0xff);
1720         } else {
1721             putc(*buf, fp);
1722         }
1723         buf++;
1724     }
1725     fflush(fp);
1726 }
1727
1728 /* Returns an errno value */
1729 int
1730 OutputMaybeTelnet(pr, message, count, outError)
1731      ProcRef pr;
1732      char *message;
1733      int count;
1734      int *outError;
1735 {
1736     char buf[8192], *p, *q, *buflim;
1737     int left, newcount, outcount;
1738
1739     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1740         *appData.gateway != NULLCHAR) {
1741         if (appData.debugMode) {
1742             fprintf(debugFP, ">ICS: ");
1743             show_bytes(debugFP, message, count);
1744             fprintf(debugFP, "\n");
1745         }
1746         return OutputToProcess(pr, message, count, outError);
1747     }
1748
1749     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1750     p = message;
1751     q = buf;
1752     left = count;
1753     newcount = 0;
1754     while (left) {
1755         if (q >= buflim) {
1756             if (appData.debugMode) {
1757                 fprintf(debugFP, ">ICS: ");
1758                 show_bytes(debugFP, buf, newcount);
1759                 fprintf(debugFP, "\n");
1760             }
1761             outcount = OutputToProcess(pr, buf, newcount, outError);
1762             if (outcount < newcount) return -1; /* to be sure */
1763             q = buf;
1764             newcount = 0;
1765         }
1766         if (*p == '\n') {
1767             *q++ = '\r';
1768             newcount++;
1769         } else if (((unsigned char) *p) == TN_IAC) {
1770             *q++ = (char) TN_IAC;
1771             newcount ++;
1772         }
1773         *q++ = *p++;
1774         newcount++;
1775         left--;
1776     }
1777     if (appData.debugMode) {
1778         fprintf(debugFP, ">ICS: ");
1779         show_bytes(debugFP, buf, newcount);
1780         fprintf(debugFP, "\n");
1781     }
1782     outcount = OutputToProcess(pr, buf, newcount, outError);
1783     if (outcount < newcount) return -1; /* to be sure */
1784     return count;
1785 }
1786
1787 void
1788 read_from_player(isr, closure, message, count, error)
1789      InputSourceRef isr;
1790      VOIDSTAR closure;
1791      char *message;
1792      int count;
1793      int error;
1794 {
1795     int outError, outCount;
1796     static int gotEof = 0;
1797
1798     /* Pass data read from player on to ICS */
1799     if (count > 0) {
1800         gotEof = 0;
1801         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1802         if (outCount < count) {
1803             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1804         }
1805     } else if (count < 0) {
1806         RemoveInputSource(isr);
1807         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1808     } else if (gotEof++ > 0) {
1809         RemoveInputSource(isr);
1810         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1811     }
1812 }
1813
1814 void
1815 KeepAlive()
1816 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1817     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1818     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1819     SendToICS("date\n");
1820     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1821 }
1822
1823 /* added routine for printf style output to ics */
1824 void ics_printf(char *format, ...)
1825 {
1826     char buffer[MSG_SIZ];
1827     va_list args;
1828
1829     va_start(args, format);
1830     vsnprintf(buffer, sizeof(buffer), format, args);
1831     buffer[sizeof(buffer)-1] = '\0';
1832     SendToICS(buffer);
1833     va_end(args);
1834 }
1835
1836 void
1837 SendToICS(s)
1838      char *s;
1839 {
1840     int count, outCount, outError;
1841
1842     if (icsPR == NULL) return;
1843
1844     count = strlen(s);
1845     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1846     if (outCount < count) {
1847         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1848     }
1849 }
1850
1851 /* This is used for sending logon scripts to the ICS. Sending
1852    without a delay causes problems when using timestamp on ICC
1853    (at least on my machine). */
1854 void
1855 SendToICSDelayed(s,msdelay)
1856      char *s;
1857      long msdelay;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NULL) return;
1862
1863     count = strlen(s);
1864     if (appData.debugMode) {
1865         fprintf(debugFP, ">ICS: ");
1866         show_bytes(debugFP, s, count);
1867         fprintf(debugFP, "\n");
1868     }
1869     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1870                                       msdelay);
1871     if (outCount < count) {
1872         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1873     }
1874 }
1875
1876
1877 /* Remove all highlighting escape sequences in s
1878    Also deletes any suffix starting with '('
1879    */
1880 char *
1881 StripHighlightAndTitle(s)
1882      char *s;
1883 {
1884     static char retbuf[MSG_SIZ];
1885     char *p = retbuf;
1886
1887     while (*s != NULLCHAR) {
1888         while (*s == '\033') {
1889             while (*s != NULLCHAR && !isalpha(*s)) s++;
1890             if (*s != NULLCHAR) s++;
1891         }
1892         while (*s != NULLCHAR && *s != '\033') {
1893             if (*s == '(' || *s == '[') {
1894                 *p = NULLCHAR;
1895                 return retbuf;
1896             }
1897             *p++ = *s++;
1898         }
1899     }
1900     *p = NULLCHAR;
1901     return retbuf;
1902 }
1903
1904 /* Remove all highlighting escape sequences in s */
1905 char *
1906 StripHighlight(s)
1907      char *s;
1908 {
1909     static char retbuf[MSG_SIZ];
1910     char *p = retbuf;
1911
1912     while (*s != NULLCHAR) {
1913         while (*s == '\033') {
1914             while (*s != NULLCHAR && !isalpha(*s)) s++;
1915             if (*s != NULLCHAR) s++;
1916         }
1917         while (*s != NULLCHAR && *s != '\033') {
1918             *p++ = *s++;
1919         }
1920     }
1921     *p = NULLCHAR;
1922     return retbuf;
1923 }
1924
1925 char *variantNames[] = VARIANT_NAMES;
1926 char *
1927 VariantName(v)
1928      VariantClass v;
1929 {
1930     return variantNames[v];
1931 }
1932
1933
1934 /* Identify a variant from the strings the chess servers use or the
1935    PGN Variant tag names we use. */
1936 VariantClass
1937 StringToVariant(e)
1938      char *e;
1939 {
1940     char *p;
1941     int wnum = -1;
1942     VariantClass v = VariantNormal;
1943     int i, found = FALSE;
1944     char buf[MSG_SIZ];
1945     int len;
1946
1947     if (!e) return v;
1948
1949     /* [HGM] skip over optional board-size prefixes */
1950     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1951         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1952         while( *e++ != '_');
1953     }
1954
1955     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1956         v = VariantNormal;
1957         found = TRUE;
1958     } else
1959     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1960       if (StrCaseStr(e, variantNames[i])) {
1961         v = (VariantClass) i;
1962         found = TRUE;
1963         break;
1964       }
1965     }
1966
1967     if (!found) {
1968       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1969           || StrCaseStr(e, "wild/fr")
1970           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1971         v = VariantFischeRandom;
1972       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1973                  (i = 1, p = StrCaseStr(e, "w"))) {
1974         p += i;
1975         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1976         if (isdigit(*p)) {
1977           wnum = atoi(p);
1978         } else {
1979           wnum = -1;
1980         }
1981         switch (wnum) {
1982         case 0: /* FICS only, actually */
1983         case 1:
1984           /* Castling legal even if K starts on d-file */
1985           v = VariantWildCastle;
1986           break;
1987         case 2:
1988         case 3:
1989         case 4:
1990           /* Castling illegal even if K & R happen to start in
1991              normal positions. */
1992           v = VariantNoCastle;
1993           break;
1994         case 5:
1995         case 7:
1996         case 8:
1997         case 10:
1998         case 11:
1999         case 12:
2000         case 13:
2001         case 14:
2002         case 15:
2003         case 18:
2004         case 19:
2005           /* Castling legal iff K & R start in normal positions */
2006           v = VariantNormal;
2007           break;
2008         case 6:
2009         case 20:
2010         case 21:
2011           /* Special wilds for position setup; unclear what to do here */
2012           v = VariantLoadable;
2013           break;
2014         case 9:
2015           /* Bizarre ICC game */
2016           v = VariantTwoKings;
2017           break;
2018         case 16:
2019           v = VariantKriegspiel;
2020           break;
2021         case 17:
2022           v = VariantLosers;
2023           break;
2024         case 22:
2025           v = VariantFischeRandom;
2026           break;
2027         case 23:
2028           v = VariantCrazyhouse;
2029           break;
2030         case 24:
2031           v = VariantBughouse;
2032           break;
2033         case 25:
2034           v = Variant3Check;
2035           break;
2036         case 26:
2037           /* Not quite the same as FICS suicide! */
2038           v = VariantGiveaway;
2039           break;
2040         case 27:
2041           v = VariantAtomic;
2042           break;
2043         case 28:
2044           v = VariantShatranj;
2045           break;
2046
2047         /* Temporary names for future ICC types.  The name *will* change in
2048            the next xboard/WinBoard release after ICC defines it. */
2049         case 29:
2050           v = Variant29;
2051           break;
2052         case 30:
2053           v = Variant30;
2054           break;
2055         case 31:
2056           v = Variant31;
2057           break;
2058         case 32:
2059           v = Variant32;
2060           break;
2061         case 33:
2062           v = Variant33;
2063           break;
2064         case 34:
2065           v = Variant34;
2066           break;
2067         case 35:
2068           v = Variant35;
2069           break;
2070         case 36:
2071           v = Variant36;
2072           break;
2073         case 37:
2074           v = VariantShogi;
2075           break;
2076         case 38:
2077           v = VariantXiangqi;
2078           break;
2079         case 39:
2080           v = VariantCourier;
2081           break;
2082         case 40:
2083           v = VariantGothic;
2084           break;
2085         case 41:
2086           v = VariantCapablanca;
2087           break;
2088         case 42:
2089           v = VariantKnightmate;
2090           break;
2091         case 43:
2092           v = VariantFairy;
2093           break;
2094         case 44:
2095           v = VariantCylinder;
2096           break;
2097         case 45:
2098           v = VariantFalcon;
2099           break;
2100         case 46:
2101           v = VariantCapaRandom;
2102           break;
2103         case 47:
2104           v = VariantBerolina;
2105           break;
2106         case 48:
2107           v = VariantJanus;
2108           break;
2109         case 49:
2110           v = VariantSuper;
2111           break;
2112         case 50:
2113           v = VariantGreat;
2114           break;
2115         case -1:
2116           /* Found "wild" or "w" in the string but no number;
2117              must assume it's normal chess. */
2118           v = VariantNormal;
2119           break;
2120         default:
2121           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2122           if( (len > MSG_SIZ) && appData.debugMode )
2123             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2124
2125           DisplayError(buf, 0);
2126           v = VariantUnknown;
2127           break;
2128         }
2129       }
2130     }
2131     if (appData.debugMode) {
2132       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2133               e, wnum, VariantName(v));
2134     }
2135     return v;
2136 }
2137
2138 static int leftover_start = 0, leftover_len = 0;
2139 char star_match[STAR_MATCH_N][MSG_SIZ];
2140
2141 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2142    advance *index beyond it, and set leftover_start to the new value of
2143    *index; else return FALSE.  If pattern contains the character '*', it
2144    matches any sequence of characters not containing '\r', '\n', or the
2145    character following the '*' (if any), and the matched sequence(s) are
2146    copied into star_match.
2147    */
2148 int
2149 looking_at(buf, index, pattern)
2150      char *buf;
2151      int *index;
2152      char *pattern;
2153 {
2154     char *bufp = &buf[*index], *patternp = pattern;
2155     int star_count = 0;
2156     char *matchp = star_match[0];
2157
2158     for (;;) {
2159         if (*patternp == NULLCHAR) {
2160             *index = leftover_start = bufp - buf;
2161             *matchp = NULLCHAR;
2162             return TRUE;
2163         }
2164         if (*bufp == NULLCHAR) return FALSE;
2165         if (*patternp == '*') {
2166             if (*bufp == *(patternp + 1)) {
2167                 *matchp = NULLCHAR;
2168                 matchp = star_match[++star_count];
2169                 patternp += 2;
2170                 bufp++;
2171                 continue;
2172             } else if (*bufp == '\n' || *bufp == '\r') {
2173                 patternp++;
2174                 if (*patternp == NULLCHAR)
2175                   continue;
2176                 else
2177                   return FALSE;
2178             } else {
2179                 *matchp++ = *bufp++;
2180                 continue;
2181             }
2182         }
2183         if (*patternp != *bufp) return FALSE;
2184         patternp++;
2185         bufp++;
2186     }
2187 }
2188
2189 void
2190 SendToPlayer(data, length)
2191      char *data;
2192      int length;
2193 {
2194     int error, outCount;
2195     outCount = OutputToProcess(NoProc, data, length, &error);
2196     if (outCount < length) {
2197         DisplayFatalError(_("Error writing to display"), error, 1);
2198     }
2199 }
2200
2201 void
2202 PackHolding(packed, holding)
2203      char packed[];
2204      char *holding;
2205 {
2206     char *p = holding;
2207     char *q = packed;
2208     int runlength = 0;
2209     int curr = 9999;
2210     do {
2211         if (*p == curr) {
2212             runlength++;
2213         } else {
2214             switch (runlength) {
2215               case 0:
2216                 break;
2217               case 1:
2218                 *q++ = curr;
2219                 break;
2220               case 2:
2221                 *q++ = curr;
2222                 *q++ = curr;
2223                 break;
2224               default:
2225                 sprintf(q, "%d", runlength);
2226                 while (*q) q++;
2227                 *q++ = curr;
2228                 break;
2229             }
2230             runlength = 1;
2231             curr = *p;
2232         }
2233     } while (*p++);
2234     *q = NULLCHAR;
2235 }
2236
2237 /* Telnet protocol requests from the front end */
2238 void
2239 TelnetRequest(ddww, option)
2240      unsigned char ddww, option;
2241 {
2242     unsigned char msg[3];
2243     int outCount, outError;
2244
2245     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2246
2247     if (appData.debugMode) {
2248         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2249         switch (ddww) {
2250           case TN_DO:
2251             ddwwStr = "DO";
2252             break;
2253           case TN_DONT:
2254             ddwwStr = "DONT";
2255             break;
2256           case TN_WILL:
2257             ddwwStr = "WILL";
2258             break;
2259           case TN_WONT:
2260             ddwwStr = "WONT";
2261             break;
2262           default:
2263             ddwwStr = buf1;
2264             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2265             break;
2266         }
2267         switch (option) {
2268           case TN_ECHO:
2269             optionStr = "ECHO";
2270             break;
2271           default:
2272             optionStr = buf2;
2273             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2274             break;
2275         }
2276         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2277     }
2278     msg[0] = TN_IAC;
2279     msg[1] = ddww;
2280     msg[2] = option;
2281     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2282     if (outCount < 3) {
2283         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2284     }
2285 }
2286
2287 void
2288 DoEcho()
2289 {
2290     if (!appData.icsActive) return;
2291     TelnetRequest(TN_DO, TN_ECHO);
2292 }
2293
2294 void
2295 DontEcho()
2296 {
2297     if (!appData.icsActive) return;
2298     TelnetRequest(TN_DONT, TN_ECHO);
2299 }
2300
2301 void
2302 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2303 {
2304     /* put the holdings sent to us by the server on the board holdings area */
2305     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2306     char p;
2307     ChessSquare piece;
2308
2309     if(gameInfo.holdingsWidth < 2)  return;
2310     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2311         return; // prevent overwriting by pre-board holdings
2312
2313     if( (int)lowestPiece >= BlackPawn ) {
2314         holdingsColumn = 0;
2315         countsColumn = 1;
2316         holdingsStartRow = BOARD_HEIGHT-1;
2317         direction = -1;
2318     } else {
2319         holdingsColumn = BOARD_WIDTH-1;
2320         countsColumn = BOARD_WIDTH-2;
2321         holdingsStartRow = 0;
2322         direction = 1;
2323     }
2324
2325     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2326         board[i][holdingsColumn] = EmptySquare;
2327         board[i][countsColumn]   = (ChessSquare) 0;
2328     }
2329     while( (p=*holdings++) != NULLCHAR ) {
2330         piece = CharToPiece( ToUpper(p) );
2331         if(piece == EmptySquare) continue;
2332         /*j = (int) piece - (int) WhitePawn;*/
2333         j = PieceToNumber(piece);
2334         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2335         if(j < 0) continue;               /* should not happen */
2336         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2337         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2338         board[holdingsStartRow+j*direction][countsColumn]++;
2339     }
2340 }
2341
2342
2343 void
2344 VariantSwitch(Board board, VariantClass newVariant)
2345 {
2346    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2347    static Board oldBoard;
2348
2349    startedFromPositionFile = FALSE;
2350    if(gameInfo.variant == newVariant) return;
2351
2352    /* [HGM] This routine is called each time an assignment is made to
2353     * gameInfo.variant during a game, to make sure the board sizes
2354     * are set to match the new variant. If that means adding or deleting
2355     * holdings, we shift the playing board accordingly
2356     * This kludge is needed because in ICS observe mode, we get boards
2357     * of an ongoing game without knowing the variant, and learn about the
2358     * latter only later. This can be because of the move list we requested,
2359     * in which case the game history is refilled from the beginning anyway,
2360     * but also when receiving holdings of a crazyhouse game. In the latter
2361     * case we want to add those holdings to the already received position.
2362     */
2363
2364
2365    if (appData.debugMode) {
2366      fprintf(debugFP, "Switch board from %s to %s\n",
2367              VariantName(gameInfo.variant), VariantName(newVariant));
2368      setbuf(debugFP, NULL);
2369    }
2370    shuffleOpenings = 0;       /* [HGM] shuffle */
2371    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2372    switch(newVariant)
2373      {
2374      case VariantShogi:
2375        newWidth = 9;  newHeight = 9;
2376        gameInfo.holdingsSize = 7;
2377      case VariantBughouse:
2378      case VariantCrazyhouse:
2379        newHoldingsWidth = 2; break;
2380      case VariantGreat:
2381        newWidth = 10;
2382      case VariantSuper:
2383        newHoldingsWidth = 2;
2384        gameInfo.holdingsSize = 8;
2385        break;
2386      case VariantGothic:
2387      case VariantCapablanca:
2388      case VariantCapaRandom:
2389        newWidth = 10;
2390      default:
2391        newHoldingsWidth = gameInfo.holdingsSize = 0;
2392      };
2393
2394    if(newWidth  != gameInfo.boardWidth  ||
2395       newHeight != gameInfo.boardHeight ||
2396       newHoldingsWidth != gameInfo.holdingsWidth ) {
2397
2398      /* shift position to new playing area, if needed */
2399      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2400        for(i=0; i<BOARD_HEIGHT; i++)
2401          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2402            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2403              board[i][j];
2404        for(i=0; i<newHeight; i++) {
2405          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2406          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2407        }
2408      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2409        for(i=0; i<BOARD_HEIGHT; i++)
2410          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2411            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2412              board[i][j];
2413      }
2414      gameInfo.boardWidth  = newWidth;
2415      gameInfo.boardHeight = newHeight;
2416      gameInfo.holdingsWidth = newHoldingsWidth;
2417      gameInfo.variant = newVariant;
2418      InitDrawingSizes(-2, 0);
2419    } else gameInfo.variant = newVariant;
2420    CopyBoard(oldBoard, board);   // remember correctly formatted board
2421      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2422    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2423 }
2424
2425 static int loggedOn = FALSE;
2426
2427 /*-- Game start info cache: --*/
2428 int gs_gamenum;
2429 char gs_kind[MSG_SIZ];
2430 static char player1Name[128] = "";
2431 static char player2Name[128] = "";
2432 static char cont_seq[] = "\n\\   ";
2433 static int player1Rating = -1;
2434 static int player2Rating = -1;
2435 /*----------------------------*/
2436
2437 ColorClass curColor = ColorNormal;
2438 int suppressKibitz = 0;
2439
2440 // [HGM] seekgraph
2441 Boolean soughtPending = FALSE;
2442 Boolean seekGraphUp;
2443 #define MAX_SEEK_ADS 200
2444 #define SQUARE 0x80
2445 char *seekAdList[MAX_SEEK_ADS];
2446 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2447 float tcList[MAX_SEEK_ADS];
2448 char colorList[MAX_SEEK_ADS];
2449 int nrOfSeekAds = 0;
2450 int minRating = 1010, maxRating = 2800;
2451 int hMargin = 10, vMargin = 20, h, w;
2452 extern int squareSize, lineGap;
2453
2454 void
2455 PlotSeekAd(int i)
2456 {
2457         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2458         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2459         if(r < minRating+100 && r >=0 ) r = minRating+100;
2460         if(r > maxRating) r = maxRating;
2461         if(tc < 1.) tc = 1.;
2462         if(tc > 95.) tc = 95.;
2463         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2464         y = ((double)r - minRating)/(maxRating - minRating)
2465             * (h-vMargin-squareSize/8-1) + vMargin;
2466         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2467         if(strstr(seekAdList[i], " u ")) color = 1;
2468         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2469            !strstr(seekAdList[i], "bullet") &&
2470            !strstr(seekAdList[i], "blitz") &&
2471            !strstr(seekAdList[i], "standard") ) color = 2;
2472         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2473         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2474 }
2475
2476 void
2477 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2478 {
2479         char buf[MSG_SIZ], *ext = "";
2480         VariantClass v = StringToVariant(type);
2481         if(strstr(type, "wild")) {
2482             ext = type + 4; // append wild number
2483             if(v == VariantFischeRandom) type = "chess960"; else
2484             if(v == VariantLoadable) type = "setup"; else
2485             type = VariantName(v);
2486         }
2487         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2488         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2489             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2490             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2491             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2492             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2493             seekNrList[nrOfSeekAds] = nr;
2494             zList[nrOfSeekAds] = 0;
2495             seekAdList[nrOfSeekAds++] = StrSave(buf);
2496             if(plot) PlotSeekAd(nrOfSeekAds-1);
2497         }
2498 }
2499
2500 void
2501 EraseSeekDot(int i)
2502 {
2503     int x = xList[i], y = yList[i], d=squareSize/4, k;
2504     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2505     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2506     // now replot every dot that overlapped
2507     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2508         int xx = xList[k], yy = yList[k];
2509         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2510             DrawSeekDot(xx, yy, colorList[k]);
2511     }
2512 }
2513
2514 void
2515 RemoveSeekAd(int nr)
2516 {
2517         int i;
2518         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2519             EraseSeekDot(i);
2520             if(seekAdList[i]) free(seekAdList[i]);
2521             seekAdList[i] = seekAdList[--nrOfSeekAds];
2522             seekNrList[i] = seekNrList[nrOfSeekAds];
2523             ratingList[i] = ratingList[nrOfSeekAds];
2524             colorList[i]  = colorList[nrOfSeekAds];
2525             tcList[i] = tcList[nrOfSeekAds];
2526             xList[i]  = xList[nrOfSeekAds];
2527             yList[i]  = yList[nrOfSeekAds];
2528             zList[i]  = zList[nrOfSeekAds];
2529             seekAdList[nrOfSeekAds] = NULL;
2530             break;
2531         }
2532 }
2533
2534 Boolean
2535 MatchSoughtLine(char *line)
2536 {
2537     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2538     int nr, base, inc, u=0; char dummy;
2539
2540     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2541        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2542        (u=1) &&
2543        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2544         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2545         // match: compact and save the line
2546         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2547         return TRUE;
2548     }
2549     return FALSE;
2550 }
2551
2552 int
2553 DrawSeekGraph()
2554 {
2555     int i;
2556     if(!seekGraphUp) return FALSE;
2557     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2558     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2559
2560     DrawSeekBackground(0, 0, w, h);
2561     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2562     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2563     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2564         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2565         yy = h-1-yy;
2566         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2567         if(i%500 == 0) {
2568             char buf[MSG_SIZ];
2569             snprintf(buf, MSG_SIZ, "%d", i);
2570             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2571         }
2572     }
2573     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2574     for(i=1; i<100; i+=(i<10?1:5)) {
2575         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2576         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2577         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2578             char buf[MSG_SIZ];
2579             snprintf(buf, MSG_SIZ, "%d", i);
2580             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2581         }
2582     }
2583     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2584     return TRUE;
2585 }
2586
2587 int SeekGraphClick(ClickType click, int x, int y, int moving)
2588 {
2589     static int lastDown = 0, displayed = 0, lastSecond;
2590     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2591         if(click == Release || moving) return FALSE;
2592         nrOfSeekAds = 0;
2593         soughtPending = TRUE;
2594         SendToICS(ics_prefix);
2595         SendToICS("sought\n"); // should this be "sought all"?
2596     } else { // issue challenge based on clicked ad
2597         int dist = 10000; int i, closest = 0, second = 0;
2598         for(i=0; i<nrOfSeekAds; i++) {
2599             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2600             if(d < dist) { dist = d; closest = i; }
2601             second += (d - zList[i] < 120); // count in-range ads
2602             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2603         }
2604         if(dist < 120) {
2605             char buf[MSG_SIZ];
2606             second = (second > 1);
2607             if(displayed != closest || second != lastSecond) {
2608                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2609                 lastSecond = second; displayed = closest;
2610             }
2611             if(click == Press) {
2612                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2613                 lastDown = closest;
2614                 return TRUE;
2615             } // on press 'hit', only show info
2616             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2617             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2618             SendToICS(ics_prefix);
2619             SendToICS(buf);
2620             return TRUE; // let incoming board of started game pop down the graph
2621         } else if(click == Release) { // release 'miss' is ignored
2622             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2623             if(moving == 2) { // right up-click
2624                 nrOfSeekAds = 0; // refresh graph
2625                 soughtPending = TRUE;
2626                 SendToICS(ics_prefix);
2627                 SendToICS("sought\n"); // should this be "sought all"?
2628             }
2629             return TRUE;
2630         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2631         // press miss or release hit 'pop down' seek graph
2632         seekGraphUp = FALSE;
2633         DrawPosition(TRUE, NULL);
2634     }
2635     return TRUE;
2636 }
2637
2638 void
2639 read_from_ics(isr, closure, data, count, error)
2640      InputSourceRef isr;
2641      VOIDSTAR closure;
2642      char *data;
2643      int count;
2644      int error;
2645 {
2646 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2647 #define STARTED_NONE 0
2648 #define STARTED_MOVES 1
2649 #define STARTED_BOARD 2
2650 #define STARTED_OBSERVE 3
2651 #define STARTED_HOLDINGS 4
2652 #define STARTED_CHATTER 5
2653 #define STARTED_COMMENT 6
2654 #define STARTED_MOVES_NOHIDE 7
2655
2656     static int started = STARTED_NONE;
2657     static char parse[20000];
2658     static int parse_pos = 0;
2659     static char buf[BUF_SIZE + 1];
2660     static int firstTime = TRUE, intfSet = FALSE;
2661     static ColorClass prevColor = ColorNormal;
2662     static int savingComment = FALSE;
2663     static int cmatch = 0; // continuation sequence match
2664     char *bp;
2665     char str[MSG_SIZ];
2666     int i, oldi;
2667     int buf_len;
2668     int next_out;
2669     int tkind;
2670     int backup;    /* [DM] For zippy color lines */
2671     char *p;
2672     char talker[MSG_SIZ]; // [HGM] chat
2673     int channel;
2674
2675     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2676
2677     if (appData.debugMode) {
2678       if (!error) {
2679         fprintf(debugFP, "<ICS: ");
2680         show_bytes(debugFP, data, count);
2681         fprintf(debugFP, "\n");
2682       }
2683     }
2684
2685     if (appData.debugMode) { int f = forwardMostMove;
2686         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2687                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2688                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2689     }
2690     if (count > 0) {
2691         /* If last read ended with a partial line that we couldn't parse,
2692            prepend it to the new read and try again. */
2693         if (leftover_len > 0) {
2694             for (i=0; i<leftover_len; i++)
2695               buf[i] = buf[leftover_start + i];
2696         }
2697
2698     /* copy new characters into the buffer */
2699     bp = buf + leftover_len;
2700     buf_len=leftover_len;
2701     for (i=0; i<count; i++)
2702     {
2703         // ignore these
2704         if (data[i] == '\r')
2705             continue;
2706
2707         // join lines split by ICS?
2708         if (!appData.noJoin)
2709         {
2710             /*
2711                 Joining just consists of finding matches against the
2712                 continuation sequence, and discarding that sequence
2713                 if found instead of copying it.  So, until a match
2714                 fails, there's nothing to do since it might be the
2715                 complete sequence, and thus, something we don't want
2716                 copied.
2717             */
2718             if (data[i] == cont_seq[cmatch])
2719             {
2720                 cmatch++;
2721                 if (cmatch == strlen(cont_seq))
2722                 {
2723                     cmatch = 0; // complete match.  just reset the counter
2724
2725                     /*
2726                         it's possible for the ICS to not include the space
2727                         at the end of the last word, making our [correct]
2728                         join operation fuse two separate words.  the server
2729                         does this when the space occurs at the width setting.
2730                     */
2731                     if (!buf_len || buf[buf_len-1] != ' ')
2732                     {
2733                         *bp++ = ' ';
2734                         buf_len++;
2735                     }
2736                 }
2737                 continue;
2738             }
2739             else if (cmatch)
2740             {
2741                 /*
2742                     match failed, so we have to copy what matched before
2743                     falling through and copying this character.  In reality,
2744                     this will only ever be just the newline character, but
2745                     it doesn't hurt to be precise.
2746                 */
2747                 strncpy(bp, cont_seq, cmatch);
2748                 bp += cmatch;
2749                 buf_len += cmatch;
2750                 cmatch = 0;
2751             }
2752         }
2753
2754         // copy this char
2755         *bp++ = data[i];
2756         buf_len++;
2757     }
2758
2759         buf[buf_len] = NULLCHAR;
2760 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2761         next_out = 0;
2762         leftover_start = 0;
2763
2764         i = 0;
2765         while (i < buf_len) {
2766             /* Deal with part of the TELNET option negotiation
2767                protocol.  We refuse to do anything beyond the
2768                defaults, except that we allow the WILL ECHO option,
2769                which ICS uses to turn off password echoing when we are
2770                directly connected to it.  We reject this option
2771                if localLineEditing mode is on (always on in xboard)
2772                and we are talking to port 23, which might be a real
2773                telnet server that will try to keep WILL ECHO on permanently.
2774              */
2775             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2776                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2777                 unsigned char option;
2778                 oldi = i;
2779                 switch ((unsigned char) buf[++i]) {
2780                   case TN_WILL:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WILL ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (remoteEchoOption) break;
2790                         if (appData.localLineEditing &&
2791                             atoi(appData.icsPort) == TN_PORT) {
2792                             TelnetRequest(TN_DONT, TN_ECHO);
2793                         } else {
2794                             EchoOff();
2795                             TelnetRequest(TN_DO, TN_ECHO);
2796                             remoteEchoOption = TRUE;
2797                         }
2798                         break;
2799                       default:
2800                         if (appData.debugMode)
2801                           fprintf(debugFP, "%d ", option);
2802                         /* Whatever this is, we don't want it. */
2803                         TelnetRequest(TN_DONT, option);
2804                         break;
2805                     }
2806                     break;
2807                   case TN_WONT:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WONT ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       case TN_ECHO:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "ECHO ");
2814                         /* Reply only if this is a change, according
2815                            to the protocol rules. */
2816                         if (!remoteEchoOption) break;
2817                         EchoOn();
2818                         TelnetRequest(TN_DONT, TN_ECHO);
2819                         remoteEchoOption = FALSE;
2820                         break;
2821                       default:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "%d ", (unsigned char) option);
2824                         /* Whatever this is, it must already be turned
2825                            off, because we never agree to turn on
2826                            anything non-default, so according to the
2827                            protocol rules, we don't reply. */
2828                         break;
2829                     }
2830                     break;
2831                   case TN_DO:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<DO ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       default:
2836                         /* Whatever this is, we refuse to do it. */
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "%d ", option);
2839                         TelnetRequest(TN_WONT, option);
2840                         break;
2841                     }
2842                     break;
2843                   case TN_DONT:
2844                     if (appData.debugMode)
2845                       fprintf(debugFP, "\n<DONT ");
2846                     switch (option = (unsigned char) buf[++i]) {
2847                       default:
2848                         if (appData.debugMode)
2849                           fprintf(debugFP, "%d ", option);
2850                         /* Whatever this is, we are already not doing
2851                            it, because we never agree to do anything
2852                            non-default, so according to the protocol
2853                            rules, we don't reply. */
2854                         break;
2855                     }
2856                     break;
2857                   case TN_IAC:
2858                     if (appData.debugMode)
2859                       fprintf(debugFP, "\n<IAC ");
2860                     /* Doubled IAC; pass it through */
2861                     i--;
2862                     break;
2863                   default:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2866                     /* Drop all other telnet commands on the floor */
2867                     break;
2868                 }
2869                 if (oldi > next_out)
2870                   SendToPlayer(&buf[next_out], oldi - next_out);
2871                 if (++i > next_out)
2872                   next_out = i;
2873                 continue;
2874             }
2875
2876             /* OK, this at least will *usually* work */
2877             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2878                 loggedOn = TRUE;
2879             }
2880
2881             if (loggedOn && !intfSet) {
2882                 if (ics_type == ICS_ICC) {
2883                   snprintf(str, MSG_SIZ,
2884                           "/set-quietly interface %s\n/set-quietly style 12\n",
2885                           programVersion);
2886                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2888                 } else if (ics_type == ICS_CHESSNET) {
2889                   snprintf(str, MSG_SIZ, "/style 12\n");
2890                 } else {
2891                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2892                   strcat(str, programVersion);
2893                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2894                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2895                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2896 #ifdef WIN32
2897                   strcat(str, "$iset nohighlight 1\n");
2898 #endif
2899                   strcat(str, "$iset lock 1\n$style 12\n");
2900                 }
2901                 SendToICS(str);
2902                 NotifyFrontendLogin();
2903                 intfSet = TRUE;
2904             }
2905
2906             if (started == STARTED_COMMENT) {
2907                 /* Accumulate characters in comment */
2908                 parse[parse_pos++] = buf[i];
2909                 if (buf[i] == '\n') {
2910                     parse[parse_pos] = NULLCHAR;
2911                     if(chattingPartner>=0) {
2912                         char mess[MSG_SIZ];
2913                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2914                         OutputChatMessage(chattingPartner, mess);
2915                         chattingPartner = -1;
2916                         next_out = i+1; // [HGM] suppress printing in ICS window
2917                     } else
2918                     if(!suppressKibitz) // [HGM] kibitz
2919                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2920                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2921                         int nrDigit = 0, nrAlph = 0, j;
2922                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2923                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2924                         parse[parse_pos] = NULLCHAR;
2925                         // try to be smart: if it does not look like search info, it should go to
2926                         // ICS interaction window after all, not to engine-output window.
2927                         for(j=0; j<parse_pos; j++) { // count letters and digits
2928                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2929                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2930                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2931                         }
2932                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2933                             int depth=0; float score;
2934                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2935                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2936                                 pvInfoList[forwardMostMove-1].depth = depth;
2937                                 pvInfoList[forwardMostMove-1].score = 100*score;
2938                             }
2939                             OutputKibitz(suppressKibitz, parse);
2940                         } else {
2941                             char tmp[MSG_SIZ];
2942                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2943                             SendToPlayer(tmp, strlen(tmp));
2944                         }
2945                         next_out = i+1; // [HGM] suppress printing in ICS window
2946                     }
2947                     started = STARTED_NONE;
2948                 } else {
2949                     /* Don't match patterns against characters in comment */
2950                     i++;
2951                     continue;
2952                 }
2953             }
2954             if (started == STARTED_CHATTER) {
2955                 if (buf[i] != '\n') {
2956                     /* Don't match patterns against characters in chatter */
2957                     i++;
2958                     continue;
2959                 }
2960                 started = STARTED_NONE;
2961                 if(suppressKibitz) next_out = i+1;
2962             }
2963
2964             /* Kludge to deal with rcmd protocol */
2965             if (firstTime && looking_at(buf, &i, "\001*")) {
2966                 DisplayFatalError(&buf[1], 0, 1);
2967                 continue;
2968             } else {
2969                 firstTime = FALSE;
2970             }
2971
2972             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2973                 ics_type = ICS_ICC;
2974                 ics_prefix = "/";
2975                 if (appData.debugMode)
2976                   fprintf(debugFP, "ics_type %d\n", ics_type);
2977                 continue;
2978             }
2979             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2980                 ics_type = ICS_FICS;
2981                 ics_prefix = "$";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2987                 ics_type = ICS_CHESSNET;
2988                 ics_prefix = "/";
2989                 if (appData.debugMode)
2990                   fprintf(debugFP, "ics_type %d\n", ics_type);
2991                 continue;
2992             }
2993
2994             if (!loggedOn &&
2995                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2996                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2997                  looking_at(buf, &i, "will be \"*\""))) {
2998               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2999               continue;
3000             }
3001
3002             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3003               char buf[MSG_SIZ];
3004               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3005               DisplayIcsInteractionTitle(buf);
3006               have_set_title = TRUE;
3007             }
3008
3009             /* skip finger notes */
3010             if (started == STARTED_NONE &&
3011                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3012                  (buf[i] == '1' && buf[i+1] == '0')) &&
3013                 buf[i+2] == ':' && buf[i+3] == ' ') {
3014               started = STARTED_CHATTER;
3015               i += 3;
3016               continue;
3017             }
3018
3019             oldi = i;
3020             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3021             if(appData.seekGraph) {
3022                 if(soughtPending && MatchSoughtLine(buf+i)) {
3023                     i = strstr(buf+i, "rated") - buf;
3024                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3025                     next_out = leftover_start = i;
3026                     started = STARTED_CHATTER;
3027                     suppressKibitz = TRUE;
3028                     continue;
3029                 }
3030                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3031                         && looking_at(buf, &i, "* ads displayed")) {
3032                     soughtPending = FALSE;
3033                     seekGraphUp = TRUE;
3034                     DrawSeekGraph();
3035                     continue;
3036                 }
3037                 if(appData.autoRefresh) {
3038                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3039                         int s = (ics_type == ICS_ICC); // ICC format differs
3040                         if(seekGraphUp)
3041                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3042                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3043                         looking_at(buf, &i, "*% "); // eat prompt
3044                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3045                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046                         next_out = i; // suppress
3047                         continue;
3048                     }
3049                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3050                         char *p = star_match[0];
3051                         while(*p) {
3052                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3053                             while(*p && *p++ != ' '); // next
3054                         }
3055                         looking_at(buf, &i, "*% "); // eat prompt
3056                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3057                         next_out = i;
3058                         continue;
3059                     }
3060                 }
3061             }
3062
3063             /* skip formula vars */
3064             if (started == STARTED_NONE &&
3065                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3066               started = STARTED_CHATTER;
3067               i += 3;
3068               continue;
3069             }
3070
3071             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3072             if (appData.autoKibitz && started == STARTED_NONE &&
3073                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3074                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3075                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3078                         suppressKibitz = TRUE;
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082                                 && (gameMode == IcsPlayingWhite)) ||
3083                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3085                             started = STARTED_CHATTER; // own kibitz we simply discard
3086                         else {
3087                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088                             parse_pos = 0; parse[0] = NULLCHAR;
3089                             savingComment = TRUE;
3090                             suppressKibitz = gameMode != IcsObserving ? 2 :
3091                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3092                         }
3093                         continue;
3094                 } else
3095                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097                          && atoi(star_match[0])) {
3098                     // suppress the acknowledgements of our own autoKibitz
3099                     char *p;
3100                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102                     SendToPlayer(star_match[0], strlen(star_match[0]));
3103                     if(looking_at(buf, &i, "*% ")) // eat prompt
3104                         suppressKibitz = FALSE;
3105                     next_out = i;
3106                     continue;
3107                 }
3108             } // [HGM] kibitz: end of patch
3109
3110             // [HGM] chat: intercept tells by users for which we have an open chat window
3111             channel = -1;
3112             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3113                                            looking_at(buf, &i, "* whispers:") ||
3114                                            looking_at(buf, &i, "* kibitzes:") ||
3115                                            looking_at(buf, &i, "* shouts:") ||
3116                                            looking_at(buf, &i, "* c-shouts:") ||
3117                                            looking_at(buf, &i, "--> * ") ||
3118                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3119                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3120                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3121                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3122                 int p;
3123                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3124                 chattingPartner = -1;
3125
3126                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3127                 for(p=0; p<MAX_CHAT; p++) {
3128                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3129                     talker[0] = '['; strcat(talker, "] ");
3130                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3131                     chattingPartner = p; break;
3132                     }
3133                 } else
3134                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3135                 for(p=0; p<MAX_CHAT; p++) {
3136                     if(!strcmp("kibitzes", chatPartner[p])) {
3137                         talker[0] = '['; strcat(talker, "] ");
3138                         chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(!strcmp("whispers", chatPartner[p])) {
3144                         talker[0] = '['; strcat(talker, "] ");
3145                         chattingPartner = p; break;
3146                     }
3147                 } else
3148                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3149                   if(buf[i-8] == '-' && buf[i-3] == 't')
3150                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3151                     if(!strcmp("c-shouts", chatPartner[p])) {
3152                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3153                         chattingPartner = p; break;
3154                     }
3155                   }
3156                   if(chattingPartner < 0)
3157                   for(p=0; p<MAX_CHAT; p++) {
3158                     if(!strcmp("shouts", chatPartner[p])) {
3159                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3160                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3161                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3162                         chattingPartner = p; break;
3163                     }
3164                   }
3165                 }
3166                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3167                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3168                     talker[0] = 0; Colorize(ColorTell, FALSE);
3169                     chattingPartner = p; break;
3170                 }
3171                 if(chattingPartner<0) i = oldi; else {
3172                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3173                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3174                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                     started = STARTED_COMMENT;
3176                     parse_pos = 0; parse[0] = NULLCHAR;
3177                     savingComment = 3 + chattingPartner; // counts as TRUE
3178                     suppressKibitz = TRUE;
3179                     continue;
3180                 }
3181             } // [HGM] chat: end of patch
3182
3183           backup = i;
3184             if (appData.zippyTalk || appData.zippyPlay) {
3185                 /* [DM] Backup address for color zippy lines */
3186 #if ZIPPY
3187                if (loggedOn == TRUE)
3188                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3189                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3190 #endif
3191             } // [DM] 'else { ' deleted
3192                 if (
3193                     /* Regular tells and says */
3194                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3195                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3196                     looking_at(buf, &i, "* says: ") ||
3197                     /* Don't color "message" or "messages" output */
3198                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3199                     looking_at(buf, &i, "*. * at *:*: ") ||
3200                     looking_at(buf, &i, "--* (*:*): ") ||
3201                     /* Message notifications (same color as tells) */
3202                     looking_at(buf, &i, "* has left a message ") ||
3203                     looking_at(buf, &i, "* just sent you a message:\n") ||
3204                     /* Whispers and kibitzes */
3205                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3206                     looking_at(buf, &i, "* kibitzes: ") ||
3207                     /* Channel tells */
3208                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3209
3210                   if (tkind == 1 && strchr(star_match[0], ':')) {
3211                       /* Avoid "tells you:" spoofs in channels */
3212                      tkind = 3;
3213                   }
3214                   if (star_match[0][0] == NULLCHAR ||
3215                       strchr(star_match[0], ' ') ||
3216                       (tkind == 3 && strchr(star_match[1], ' '))) {
3217                     /* Reject bogus matches */
3218                     i = oldi;
3219                   } else {
3220                     if (appData.colorize) {
3221                       if (oldi > next_out) {
3222                         SendToPlayer(&buf[next_out], oldi - next_out);
3223                         next_out = oldi;
3224                       }
3225                       switch (tkind) {
3226                       case 1:
3227                         Colorize(ColorTell, FALSE);
3228                         curColor = ColorTell;
3229                         break;
3230                       case 2:
3231                         Colorize(ColorKibitz, FALSE);
3232                         curColor = ColorKibitz;
3233                         break;
3234                       case 3:
3235                         p = strrchr(star_match[1], '(');
3236                         if (p == NULL) {
3237                           p = star_match[1];
3238                         } else {
3239                           p++;
3240                         }
3241                         if (atoi(p) == 1) {
3242                           Colorize(ColorChannel1, FALSE);
3243                           curColor = ColorChannel1;
3244                         } else {
3245                           Colorize(ColorChannel, FALSE);
3246                           curColor = ColorChannel;
3247                         }
3248                         break;
3249                       case 5:
3250                         curColor = ColorNormal;
3251                         break;
3252                       }
3253                     }
3254                     if (started == STARTED_NONE && appData.autoComment &&
3255                         (gameMode == IcsObserving ||
3256                          gameMode == IcsPlayingWhite ||
3257                          gameMode == IcsPlayingBlack)) {
3258                       parse_pos = i - oldi;
3259                       memcpy(parse, &buf[oldi], parse_pos);
3260                       parse[parse_pos] = NULLCHAR;
3261                       started = STARTED_COMMENT;
3262                       savingComment = TRUE;
3263                     } else {
3264                       started = STARTED_CHATTER;
3265                       savingComment = FALSE;
3266                     }
3267                     loggedOn = TRUE;
3268                     continue;
3269                   }
3270                 }
3271
3272                 if (looking_at(buf, &i, "* s-shouts: ") ||
3273                     looking_at(buf, &i, "* c-shouts: ")) {
3274                     if (appData.colorize) {
3275                         if (oldi > next_out) {
3276                             SendToPlayer(&buf[next_out], oldi - next_out);
3277                             next_out = oldi;
3278                         }
3279                         Colorize(ColorSShout, FALSE);
3280                         curColor = ColorSShout;
3281                     }
3282                     loggedOn = TRUE;
3283                     started = STARTED_CHATTER;
3284                     continue;
3285                 }
3286
3287                 if (looking_at(buf, &i, "--->")) {
3288                     loggedOn = TRUE;
3289                     continue;
3290                 }
3291
3292                 if (looking_at(buf, &i, "* shouts: ") ||
3293                     looking_at(buf, &i, "--> ")) {
3294                     if (appData.colorize) {
3295                         if (oldi > next_out) {
3296                             SendToPlayer(&buf[next_out], oldi - next_out);
3297                             next_out = oldi;
3298                         }
3299                         Colorize(ColorShout, FALSE);
3300                         curColor = ColorShout;
3301                     }
3302                     loggedOn = TRUE;
3303                     started = STARTED_CHATTER;
3304                     continue;
3305                 }
3306
3307                 if (looking_at( buf, &i, "Challenge:")) {
3308                     if (appData.colorize) {
3309                         if (oldi > next_out) {
3310                             SendToPlayer(&buf[next_out], oldi - next_out);
3311                             next_out = oldi;
3312                         }
3313                         Colorize(ColorChallenge, FALSE);
3314                         curColor = ColorChallenge;
3315                     }
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* offers you") ||
3321                     looking_at(buf, &i, "* offers to be") ||
3322                     looking_at(buf, &i, "* would like to") ||
3323                     looking_at(buf, &i, "* requests to") ||
3324                     looking_at(buf, &i, "Your opponent offers") ||
3325                     looking_at(buf, &i, "Your opponent requests")) {
3326
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorRequest, FALSE);
3333                         curColor = ColorRequest;
3334                     }
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* (*) seeking")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorSeek, FALSE);
3345                         curColor = ColorSeek;
3346                     }
3347                     continue;
3348             }
3349
3350           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3351
3352             if (looking_at(buf, &i, "\\   ")) {
3353                 if (prevColor != ColorNormal) {
3354                     if (oldi > next_out) {
3355                         SendToPlayer(&buf[next_out], oldi - next_out);
3356                         next_out = oldi;
3357                     }
3358                     Colorize(prevColor, TRUE);
3359                     curColor = prevColor;
3360                 }
3361                 if (savingComment) {
3362                     parse_pos = i - oldi;
3363                     memcpy(parse, &buf[oldi], parse_pos);
3364                     parse[parse_pos] = NULLCHAR;
3365                     started = STARTED_COMMENT;
3366                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3367                         chattingPartner = savingComment - 3; // kludge to remember the box
3368                 } else {
3369                     started = STARTED_CHATTER;
3370                 }
3371                 continue;
3372             }
3373
3374             if (looking_at(buf, &i, "Black Strength :") ||
3375                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3376                 looking_at(buf, &i, "<10>") ||
3377                 looking_at(buf, &i, "#@#")) {
3378                 /* Wrong board style */
3379                 loggedOn = TRUE;
3380                 SendToICS(ics_prefix);
3381                 SendToICS("set style 12\n");
3382                 SendToICS(ics_prefix);
3383                 SendToICS("refresh\n");
3384                 continue;
3385             }
3386
3387             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3388                 ICSInitScript();
3389                 have_sent_ICS_logon = 1;
3390                 continue;
3391             }
3392
3393             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3394                 (looking_at(buf, &i, "\n<12> ") ||
3395                  looking_at(buf, &i, "<12> "))) {
3396                 loggedOn = TRUE;
3397                 if (oldi > next_out) {
3398                     SendToPlayer(&buf[next_out], oldi - next_out);
3399                 }
3400                 next_out = i;
3401                 started = STARTED_BOARD;
3402                 parse_pos = 0;
3403                 continue;
3404             }
3405
3406             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3407                 looking_at(buf, &i, "<b1> ")) {
3408                 if (oldi > next_out) {
3409                     SendToPlayer(&buf[next_out], oldi - next_out);
3410                 }
3411                 next_out = i;
3412                 started = STARTED_HOLDINGS;
3413                 parse_pos = 0;
3414                 continue;
3415             }
3416
3417             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3418                 loggedOn = TRUE;
3419                 /* Header for a move list -- first line */
3420
3421                 switch (ics_getting_history) {
3422                   case H_FALSE:
3423                     switch (gameMode) {
3424                       case IcsIdle:
3425                       case BeginningOfGame:
3426                         /* User typed "moves" or "oldmoves" while we
3427                            were idle.  Pretend we asked for these
3428                            moves and soak them up so user can step
3429                            through them and/or save them.
3430                            */
3431                         Reset(FALSE, TRUE);
3432                         gameMode = IcsObserving;
3433                         ModeHighlight();
3434                         ics_gamenum = -1;
3435                         ics_getting_history = H_GOT_UNREQ_HEADER;
3436                         break;
3437                       case EditGame: /*?*/
3438                       case EditPosition: /*?*/
3439                         /* Should above feature work in these modes too? */
3440                         /* For now it doesn't */
3441                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3442                         break;
3443                       default:
3444                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3445                         break;
3446                     }
3447                     break;
3448                   case H_REQUESTED:
3449                     /* Is this the right one? */
3450                     if (gameInfo.white && gameInfo.black &&
3451                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3452                         strcmp(gameInfo.black, star_match[2]) == 0) {
3453                         /* All is well */
3454                         ics_getting_history = H_GOT_REQ_HEADER;
3455                     }
3456                     break;
3457                   case H_GOT_REQ_HEADER:
3458                   case H_GOT_UNREQ_HEADER:
3459                   case H_GOT_UNWANTED_HEADER:
3460                   case H_GETTING_MOVES:
3461                     /* Should not happen */
3462                     DisplayError(_("Error gathering move list: two headers"), 0);
3463                     ics_getting_history = H_FALSE;
3464                     break;
3465                 }
3466
3467                 /* Save player ratings into gameInfo if needed */
3468                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3469                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3470                     (gameInfo.whiteRating == -1 ||
3471                      gameInfo.blackRating == -1)) {
3472
3473                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3474                     gameInfo.blackRating = string_to_rating(star_match[3]);
3475                     if (appData.debugMode)
3476                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3477                               gameInfo.whiteRating, gameInfo.blackRating);
3478                 }
3479                 continue;
3480             }
3481
3482             if (looking_at(buf, &i,
3483               "* * match, initial time: * minute*, increment: * second")) {
3484                 /* Header for a move list -- second line */
3485                 /* Initial board will follow if this is a wild game */
3486                 if (gameInfo.event != NULL) free(gameInfo.event);
3487                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3488                 gameInfo.event = StrSave(str);
3489                 /* [HGM] we switched variant. Translate boards if needed. */
3490                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3491                 continue;
3492             }
3493
3494             if (looking_at(buf, &i, "Move  ")) {
3495                 /* Beginning of a move list */
3496                 switch (ics_getting_history) {
3497                   case H_FALSE:
3498                     /* Normally should not happen */
3499                     /* Maybe user hit reset while we were parsing */
3500                     break;
3501                   case H_REQUESTED:
3502                     /* Happens if we are ignoring a move list that is not
3503                      * the one we just requested.  Common if the user
3504                      * tries to observe two games without turning off
3505                      * getMoveList */
3506                     break;
3507                   case H_GETTING_MOVES:
3508                     /* Should not happen */
3509                     DisplayError(_("Error gathering move list: nested"), 0);
3510                     ics_getting_history = H_FALSE;
3511                     break;
3512                   case H_GOT_REQ_HEADER:
3513                     ics_getting_history = H_GETTING_MOVES;
3514                     started = STARTED_MOVES;
3515                     parse_pos = 0;
3516                     if (oldi > next_out) {
3517                         SendToPlayer(&buf[next_out], oldi - next_out);
3518                     }
3519                     break;
3520                   case H_GOT_UNREQ_HEADER:
3521                     ics_getting_history = H_GETTING_MOVES;
3522                     started = STARTED_MOVES_NOHIDE;
3523                     parse_pos = 0;
3524                     break;
3525                   case H_GOT_UNWANTED_HEADER:
3526                     ics_getting_history = H_FALSE;
3527                     break;
3528                 }
3529                 continue;
3530             }
3531
3532             if (looking_at(buf, &i, "% ") ||
3533                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3534                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3535                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3536                     soughtPending = FALSE;
3537                     seekGraphUp = TRUE;
3538                     DrawSeekGraph();
3539                 }
3540                 if(suppressKibitz) next_out = i;
3541                 savingComment = FALSE;
3542                 suppressKibitz = 0;
3543                 switch (started) {
3544                   case STARTED_MOVES:
3545                   case STARTED_MOVES_NOHIDE:
3546                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3547                     parse[parse_pos + i - oldi] = NULLCHAR;
3548                     ParseGameHistory(parse);
3549 #if ZIPPY
3550                     if (appData.zippyPlay && first.initDone) {
3551                         FeedMovesToProgram(&first, forwardMostMove);
3552                         if (gameMode == IcsPlayingWhite) {
3553                             if (WhiteOnMove(forwardMostMove)) {
3554                                 if (first.sendTime) {
3555                                   if (first.useColors) {
3556                                     SendToProgram("black\n", &first);
3557                                   }
3558                                   SendTimeRemaining(&first, TRUE);
3559                                 }
3560                                 if (first.useColors) {
3561                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3562                                 }
3563                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3564                                 first.maybeThinking = TRUE;
3565                             } else {
3566                                 if (first.usePlayother) {
3567                                   if (first.sendTime) {
3568                                     SendTimeRemaining(&first, TRUE);
3569                                   }
3570                                   SendToProgram("playother\n", &first);
3571                                   firstMove = FALSE;
3572                                 } else {
3573                                   firstMove = TRUE;
3574                                 }
3575                             }
3576                         } else if (gameMode == IcsPlayingBlack) {
3577                             if (!WhiteOnMove(forwardMostMove)) {
3578                                 if (first.sendTime) {
3579                                   if (first.useColors) {
3580                                     SendToProgram("white\n", &first);
3581                                   }
3582                                   SendTimeRemaining(&first, FALSE);
3583                                 }
3584                                 if (first.useColors) {
3585                                   SendToProgram("black\n", &first);
3586                                 }
3587                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3588                                 first.maybeThinking = TRUE;
3589                             } else {
3590                                 if (first.usePlayother) {
3591                                   if (first.sendTime) {
3592                                     SendTimeRemaining(&first, FALSE);
3593                                   }
3594                                   SendToProgram("playother\n", &first);
3595                                   firstMove = FALSE;
3596                                 } else {
3597                                   firstMove = TRUE;
3598                                 }
3599                             }
3600                         }
3601                     }
3602 #endif
3603                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3604                         /* Moves came from oldmoves or moves command
3605                            while we weren't doing anything else.
3606                            */
3607                         currentMove = forwardMostMove;
3608                         ClearHighlights();/*!!could figure this out*/
3609                         flipView = appData.flipView;
3610                         DrawPosition(TRUE, boards[currentMove]);
3611                         DisplayBothClocks();
3612                         snprintf(str, MSG_SIZ, "%s vs. %s",
3613                                 gameInfo.white, gameInfo.black);
3614                         DisplayTitle(str);
3615                         gameMode = IcsIdle;
3616                     } else {
3617                         /* Moves were history of an active game */
3618                         if (gameInfo.resultDetails != NULL) {
3619                             free(gameInfo.resultDetails);
3620                             gameInfo.resultDetails = NULL;
3621                         }
3622                     }
3623                     HistorySet(parseList, backwardMostMove,
3624                                forwardMostMove, currentMove-1);
3625                     DisplayMove(currentMove - 1);
3626                     if (started == STARTED_MOVES) next_out = i;
3627                     started = STARTED_NONE;
3628                     ics_getting_history = H_FALSE;
3629                     break;
3630
3631                   case STARTED_OBSERVE:
3632                     started = STARTED_NONE;
3633                     SendToICS(ics_prefix);
3634                     SendToICS("refresh\n");
3635                     break;
3636
3637                   default:
3638                     break;
3639                 }
3640                 if(bookHit) { // [HGM] book: simulate book reply
3641                     static char bookMove[MSG_SIZ]; // a bit generous?
3642
3643                     programStats.nodes = programStats.depth = programStats.time =
3644                     programStats.score = programStats.got_only_move = 0;
3645                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3646
3647                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3648                     strcat(bookMove, bookHit);
3649                     HandleMachineMove(bookMove, &first);
3650                 }
3651                 continue;
3652             }
3653
3654             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3655                  started == STARTED_HOLDINGS ||
3656                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3657                 /* Accumulate characters in move list or board */
3658                 parse[parse_pos++] = buf[i];
3659             }
3660
3661             /* Start of game messages.  Mostly we detect start of game
3662                when the first board image arrives.  On some versions
3663                of the ICS, though, we need to do a "refresh" after starting
3664                to observe in order to get the current board right away. */
3665             if (looking_at(buf, &i, "Adding game * to observation list")) {
3666                 started = STARTED_OBSERVE;
3667                 continue;
3668             }
3669
3670             /* Handle auto-observe */
3671             if (appData.autoObserve &&
3672                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3673                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3674                 char *player;
3675                 /* Choose the player that was highlighted, if any. */
3676                 if (star_match[0][0] == '\033' ||
3677                     star_match[1][0] != '\033') {
3678                     player = star_match[0];
3679                 } else {
3680                     player = star_match[2];
3681                 }
3682                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3683                         ics_prefix, StripHighlightAndTitle(player));
3684                 SendToICS(str);
3685
3686                 /* Save ratings from notify string */
3687                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3688                 player1Rating = string_to_rating(star_match[1]);
3689                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3690                 player2Rating = string_to_rating(star_match[3]);
3691
3692                 if (appData.debugMode)
3693                   fprintf(debugFP,
3694                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3695                           player1Name, player1Rating,
3696                           player2Name, player2Rating);
3697
3698                 continue;
3699             }
3700
3701             /* Deal with automatic examine mode after a game,
3702                and with IcsObserving -> IcsExamining transition */
3703             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3704                 looking_at(buf, &i, "has made you an examiner of game *")) {
3705
3706                 int gamenum = atoi(star_match[0]);
3707                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3708                     gamenum == ics_gamenum) {
3709                     /* We were already playing or observing this game;
3710                        no need to refetch history */
3711                     gameMode = IcsExamining;
3712                     if (pausing) {
3713                         pauseExamForwardMostMove = forwardMostMove;
3714                     } else if (currentMove < forwardMostMove) {
3715                         ForwardInner(forwardMostMove);
3716                     }
3717                 } else {
3718                     /* I don't think this case really can happen */
3719                     SendToICS(ics_prefix);
3720                     SendToICS("refresh\n");
3721                 }
3722                 continue;
3723             }
3724
3725             /* Error messages */
3726 //          if (ics_user_moved) {
3727             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3728                 if (looking_at(buf, &i, "Illegal move") ||
3729                     looking_at(buf, &i, "Not a legal move") ||
3730                     looking_at(buf, &i, "Your king is in check") ||
3731                     looking_at(buf, &i, "It isn't your turn") ||
3732                     looking_at(buf, &i, "It is not your move")) {
3733                     /* Illegal move */
3734                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3735                         currentMove = forwardMostMove-1;
3736                         DisplayMove(currentMove - 1); /* before DMError */
3737                         DrawPosition(FALSE, boards[currentMove]);
3738                         SwitchClocks(forwardMostMove-1); // [HGM] race
3739                         DisplayBothClocks();
3740                     }
3741                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3742                     ics_user_moved = 0;
3743                     continue;
3744                 }
3745             }
3746
3747             if (looking_at(buf, &i, "still have time") ||
3748                 looking_at(buf, &i, "not out of time") ||
3749                 looking_at(buf, &i, "either player is out of time") ||
3750                 looking_at(buf, &i, "has timeseal; checking")) {
3751                 /* We must have called his flag a little too soon */
3752                 whiteFlag = blackFlag = FALSE;
3753                 continue;
3754             }
3755
3756             if (looking_at(buf, &i, "added * seconds to") ||
3757                 looking_at(buf, &i, "seconds were added to")) {
3758                 /* Update the clocks */
3759                 SendToICS(ics_prefix);
3760                 SendToICS("refresh\n");
3761                 continue;
3762             }
3763
3764             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3765                 ics_clock_paused = TRUE;
3766                 StopClocks();
3767                 continue;
3768             }
3769
3770             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3771                 ics_clock_paused = FALSE;
3772                 StartClocks();
3773                 continue;
3774             }
3775
3776             /* Grab player ratings from the Creating: message.
3777                Note we have to check for the special case when
3778                the ICS inserts things like [white] or [black]. */
3779             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3780                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3781                 /* star_matches:
3782                    0    player 1 name (not necessarily white)
3783                    1    player 1 rating
3784                    2    empty, white, or black (IGNORED)
3785                    3    player 2 name (not necessarily black)
3786                    4    player 2 rating
3787
3788                    The names/ratings are sorted out when the game
3789                    actually starts (below).
3790                 */
3791                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3792                 player1Rating = string_to_rating(star_match[1]);
3793                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3794                 player2Rating = string_to_rating(star_match[4]);
3795
3796                 if (appData.debugMode)
3797                   fprintf(debugFP,
3798                           "Ratings from 'Creating:' %s %d, %s %d\n",
3799                           player1Name, player1Rating,
3800                           player2Name, player2Rating);
3801
3802                 continue;
3803             }
3804
3805             /* Improved generic start/end-of-game messages */
3806             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3807                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3808                 /* If tkind == 0: */
3809                 /* star_match[0] is the game number */
3810                 /*           [1] is the white player's name */
3811                 /*           [2] is the black player's name */
3812                 /* For end-of-game: */
3813                 /*           [3] is the reason for the game end */
3814                 /*           [4] is a PGN end game-token, preceded by " " */
3815                 /* For start-of-game: */
3816                 /*           [3] begins with "Creating" or "Continuing" */
3817                 /*           [4] is " *" or empty (don't care). */
3818                 int gamenum = atoi(star_match[0]);
3819                 char *whitename, *blackname, *why, *endtoken;
3820                 ChessMove endtype = EndOfFile;
3821
3822                 if (tkind == 0) {
3823                   whitename = star_match[1];
3824                   blackname = star_match[2];
3825                   why = star_match[3];
3826                   endtoken = star_match[4];
3827                 } else {
3828                   whitename = star_match[1];
3829                   blackname = star_match[3];
3830                   why = star_match[5];
3831                   endtoken = star_match[6];
3832                 }
3833
3834                 /* Game start messages */
3835                 if (strncmp(why, "Creating ", 9) == 0 ||
3836                     strncmp(why, "Continuing ", 11) == 0) {
3837                     gs_gamenum = gamenum;
3838                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3839                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3840 #if ZIPPY
3841                     if (appData.zippyPlay) {
3842                         ZippyGameStart(whitename, blackname);
3843                     }
3844 #endif /*ZIPPY*/
3845                     partnerBoardValid = FALSE; // [HGM] bughouse
3846                     continue;
3847                 }
3848
3849                 /* Game end messages */
3850                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3851                     ics_gamenum != gamenum) {
3852                     continue;
3853                 }
3854                 while (endtoken[0] == ' ') endtoken++;
3855                 switch (endtoken[0]) {
3856                   case '*':
3857                   default:
3858                     endtype = GameUnfinished;
3859                     break;
3860                   case '0':
3861                     endtype = BlackWins;
3862                     break;
3863                   case '1':
3864                     if (endtoken[1] == '/')
3865                       endtype = GameIsDrawn;
3866                     else
3867                       endtype = WhiteWins;
3868                     break;
3869                 }
3870                 GameEnds(endtype, why, GE_ICS);
3871 #if ZIPPY
3872                 if (appData.zippyPlay && first.initDone) {
3873                     ZippyGameEnd(endtype, why);
3874                     if (first.pr == NULL) {
3875                       /* Start the next process early so that we'll
3876                          be ready for the next challenge */
3877                       StartChessProgram(&first);
3878                     }
3879                     /* Send "new" early, in case this command takes
3880                        a long time to finish, so that we'll be ready
3881                        for the next challenge. */
3882                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3883                     Reset(TRUE, TRUE);
3884                 }
3885 #endif /*ZIPPY*/
3886                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3887                 continue;
3888             }
3889
3890             if (looking_at(buf, &i, "Removing game * from observation") ||
3891                 looking_at(buf, &i, "no longer observing game *") ||
3892                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3893                 if (gameMode == IcsObserving &&
3894                     atoi(star_match[0]) == ics_gamenum)
3895                   {
3896                       /* icsEngineAnalyze */
3897                       if (appData.icsEngineAnalyze) {
3898                             ExitAnalyzeMode();
3899                             ModeHighlight();
3900                       }
3901                       StopClocks();
3902                       gameMode = IcsIdle;
3903                       ics_gamenum = -1;
3904                       ics_user_moved = FALSE;
3905                   }
3906                 continue;
3907             }
3908
3909             if (looking_at(buf, &i, "no longer examining game *")) {
3910                 if (gameMode == IcsExamining &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       gameMode = IcsIdle;
3914                       ics_gamenum = -1;
3915                       ics_user_moved = FALSE;
3916                   }
3917                 continue;
3918             }
3919
3920             /* Advance leftover_start past any newlines we find,
3921                so only partial lines can get reparsed */
3922             if (looking_at(buf, &i, "\n")) {
3923                 prevColor = curColor;
3924                 if (curColor != ColorNormal) {
3925                     if (oldi > next_out) {
3926                         SendToPlayer(&buf[next_out], oldi - next_out);
3927                         next_out = oldi;
3928                     }
3929                     Colorize(ColorNormal, FALSE);
3930                     curColor = ColorNormal;
3931                 }
3932                 if (started == STARTED_BOARD) {
3933                     started = STARTED_NONE;
3934                     parse[parse_pos] = NULLCHAR;
3935                     ParseBoard12(parse);
3936                     ics_user_moved = 0;
3937
3938                     /* Send premove here */
3939                     if (appData.premove) {
3940                       char str[MSG_SIZ];
3941                       if (currentMove == 0 &&
3942                           gameMode == IcsPlayingWhite &&
3943                           appData.premoveWhite) {
3944                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3945                         if (appData.debugMode)
3946                           fprintf(debugFP, "Sending premove:\n");
3947                         SendToICS(str);
3948                       } else if (currentMove == 1 &&
3949                                  gameMode == IcsPlayingBlack &&
3950                                  appData.premoveBlack) {
3951                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3952                         if (appData.debugMode)
3953                           fprintf(debugFP, "Sending premove:\n");
3954                         SendToICS(str);
3955                       } else if (gotPremove) {
3956                         gotPremove = 0;
3957                         ClearPremoveHighlights();
3958                         if (appData.debugMode)
3959                           fprintf(debugFP, "Sending premove:\n");
3960                           UserMoveEvent(premoveFromX, premoveFromY,
3961                                         premoveToX, premoveToY,
3962                                         premovePromoChar);
3963                       }
3964                     }
3965
3966                     /* Usually suppress following prompt */
3967                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3968                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3969                         if (looking_at(buf, &i, "*% ")) {
3970                             savingComment = FALSE;
3971                             suppressKibitz = 0;
3972                         }
3973                     }
3974                     next_out = i;
3975                 } else if (started == STARTED_HOLDINGS) {
3976                     int gamenum;
3977                     char new_piece[MSG_SIZ];
3978                     started = STARTED_NONE;
3979                     parse[parse_pos] = NULLCHAR;
3980                     if (appData.debugMode)
3981                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3982                                                         parse, currentMove);
3983                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3984                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3985                         if (gameInfo.variant == VariantNormal) {
3986                           /* [HGM] We seem to switch variant during a game!
3987                            * Presumably no holdings were displayed, so we have
3988                            * to move the position two files to the right to
3989                            * create room for them!
3990                            */
3991                           VariantClass newVariant;
3992                           switch(gameInfo.boardWidth) { // base guess on board width
3993                                 case 9:  newVariant = VariantShogi; break;
3994                                 case 10: newVariant = VariantGreat; break;
3995                                 default: newVariant = VariantCrazyhouse; break;
3996                           }
3997                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3998                           /* Get a move list just to see the header, which
3999                              will tell us whether this is really bug or zh */
4000                           if (ics_getting_history == H_FALSE) {
4001                             ics_getting_history = H_REQUESTED;
4002                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4003                             SendToICS(str);
4004                           }
4005                         }
4006                         new_piece[0] = NULLCHAR;
4007                         sscanf(parse, "game %d white [%s black [%s <- %s",
4008                                &gamenum, white_holding, black_holding,
4009                                new_piece);
4010                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4011                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4012                         /* [HGM] copy holdings to board holdings area */
4013                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4014                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4015                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4016 #if ZIPPY
4017                         if (appData.zippyPlay && first.initDone) {
4018                             ZippyHoldings(white_holding, black_holding,
4019                                           new_piece);
4020                         }
4021 #endif /*ZIPPY*/
4022                         if (tinyLayout || smallLayout) {
4023                             char wh[16], bh[16];
4024                             PackHolding(wh, white_holding);
4025                             PackHolding(bh, black_holding);
4026                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4027                                     gameInfo.white, gameInfo.black);
4028                         } else {
4029                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4030                                     gameInfo.white, white_holding,
4031                                     gameInfo.black, black_holding);
4032                         }
4033                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4034                         DrawPosition(FALSE, boards[currentMove]);
4035                         DisplayTitle(str);
4036                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4037                         sscanf(parse, "game %d white [%s black [%s <- %s",
4038                                &gamenum, white_holding, black_holding,
4039                                new_piece);
4040                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4041                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4042                         /* [HGM] copy holdings to partner-board holdings area */
4043                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4044                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4045                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4046                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4047                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4048                       }
4049                     }
4050                     /* Suppress following prompt */
4051                     if (looking_at(buf, &i, "*% ")) {
4052                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4053                         savingComment = FALSE;
4054                         suppressKibitz = 0;
4055                     }
4056                     next_out = i;
4057                 }
4058                 continue;
4059             }
4060
4061             i++;                /* skip unparsed character and loop back */
4062         }
4063
4064         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4065 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4066 //          SendToPlayer(&buf[next_out], i - next_out);
4067             started != STARTED_HOLDINGS && leftover_start > next_out) {
4068             SendToPlayer(&buf[next_out], leftover_start - next_out);
4069             next_out = i;
4070         }
4071
4072         leftover_len = buf_len - leftover_start;
4073         /* if buffer ends with something we couldn't parse,
4074            reparse it after appending the next read */
4075
4076     } else if (count == 0) {
4077         RemoveInputSource(isr);
4078         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4079     } else {
4080         DisplayFatalError(_("Error reading from ICS"), error, 1);
4081     }
4082 }
4083
4084
4085 /* Board style 12 looks like this:
4086
4087    <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
4088
4089  * The "<12> " is stripped before it gets to this routine.  The two
4090  * trailing 0's (flip state and clock ticking) are later addition, and
4091  * some chess servers may not have them, or may have only the first.
4092  * Additional trailing fields may be added in the future.
4093  */
4094
4095 #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"
4096
4097 #define RELATION_OBSERVING_PLAYED    0
4098 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4099 #define RELATION_PLAYING_MYMOVE      1
4100 #define RELATION_PLAYING_NOTMYMOVE  -1
4101 #define RELATION_EXAMINING           2
4102 #define RELATION_ISOLATED_BOARD     -3
4103 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4104
4105 void
4106 ParseBoard12(string)
4107      char *string;
4108 {
4109     GameMode newGameMode;
4110     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4111     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4112     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4113     char to_play, board_chars[200];
4114     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4115     char black[32], white[32];
4116     Board board;
4117     int prevMove = currentMove;
4118     int ticking = 2;
4119     ChessMove moveType;
4120     int fromX, fromY, toX, toY;
4121     char promoChar;
4122     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4123     char *bookHit = NULL; // [HGM] book
4124     Boolean weird = FALSE, reqFlag = FALSE;
4125
4126     fromX = fromY = toX = toY = -1;
4127
4128     newGame = FALSE;
4129
4130     if (appData.debugMode)
4131       fprintf(debugFP, _("Parsing board: %s\n"), string);
4132
4133     move_str[0] = NULLCHAR;
4134     elapsed_time[0] = NULLCHAR;
4135     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4136         int  i = 0, j;
4137         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4138             if(string[i] == ' ') { ranks++; files = 0; }
4139             else files++;
4140             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4141             i++;
4142         }
4143         for(j = 0; j <i; j++) board_chars[j] = string[j];
4144         board_chars[i] = '\0';
4145         string += i + 1;
4146     }
4147     n = sscanf(string, PATTERN, &to_play, &double_push,
4148                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4149                &gamenum, white, black, &relation, &basetime, &increment,
4150                &white_stren, &black_stren, &white_time, &black_time,
4151                &moveNum, str, elapsed_time, move_str, &ics_flip,
4152                &ticking);
4153
4154     if (n < 21) {
4155         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4156         DisplayError(str, 0);
4157         return;
4158     }
4159
4160     /* Convert the move number to internal form */
4161     moveNum = (moveNum - 1) * 2;
4162     if (to_play == 'B') moveNum++;
4163     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4164       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4165                         0, 1);
4166       return;
4167     }
4168
4169     switch (relation) {
4170       case RELATION_OBSERVING_PLAYED:
4171       case RELATION_OBSERVING_STATIC:
4172         if (gamenum == -1) {
4173             /* Old ICC buglet */
4174             relation = RELATION_OBSERVING_STATIC;
4175         }
4176         newGameMode = IcsObserving;
4177         break;
4178       case RELATION_PLAYING_MYMOVE:
4179       case RELATION_PLAYING_NOTMYMOVE:
4180         newGameMode =
4181           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4182             IcsPlayingWhite : IcsPlayingBlack;
4183         break;
4184       case RELATION_EXAMINING:
4185         newGameMode = IcsExamining;
4186         break;
4187       case RELATION_ISOLATED_BOARD:
4188       default:
4189         /* Just display this board.  If user was doing something else,
4190            we will forget about it until the next board comes. */
4191         newGameMode = IcsIdle;
4192         break;
4193       case RELATION_STARTING_POSITION:
4194         newGameMode = gameMode;
4195         break;
4196     }
4197
4198     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4199          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4200       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4201       char *toSqr;
4202       for (k = 0; k < ranks; k++) {
4203         for (j = 0; j < files; j++)
4204           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4205         if(gameInfo.holdingsWidth > 1) {
4206              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4207              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4208         }
4209       }
4210       CopyBoard(partnerBoard, board);
4211       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4212         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4213         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4214       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4215       if(toSqr = strchr(str, '-')) {
4216         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4217         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4218       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4219       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4220       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4221       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4222       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4223       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4224                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4225       DisplayMessage(partnerStatus, "");
4226         partnerBoardValid = TRUE;
4227       return;
4228     }
4229
4230     /* Modify behavior for initial board display on move listing
4231        of wild games.
4232        */
4233     switch (ics_getting_history) {
4234       case H_FALSE:
4235       case H_REQUESTED:
4236         break;
4237       case H_GOT_REQ_HEADER:
4238       case H_GOT_UNREQ_HEADER:
4239         /* This is the initial position of the current game */
4240         gamenum = ics_gamenum;
4241         moveNum = 0;            /* old ICS bug workaround */
4242         if (to_play == 'B') {
4243           startedFromSetupPosition = TRUE;
4244           blackPlaysFirst = TRUE;
4245           moveNum = 1;
4246           if (forwardMostMove == 0) forwardMostMove = 1;
4247           if (backwardMostMove == 0) backwardMostMove = 1;
4248           if (currentMove == 0) currentMove = 1;
4249         }
4250         newGameMode = gameMode;
4251         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4252         break;
4253       case H_GOT_UNWANTED_HEADER:
4254         /* This is an initial board that we don't want */
4255         return;
4256       case H_GETTING_MOVES:
4257         /* Should not happen */
4258         DisplayError(_("Error gathering move list: extra board"), 0);
4259         ics_getting_history = H_FALSE;
4260         return;
4261     }
4262
4263    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4264                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4265      /* [HGM] We seem to have switched variant unexpectedly
4266       * Try to guess new variant from board size
4267       */
4268           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4269           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4270           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4271           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4272           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4273           if(!weird) newVariant = VariantNormal;
4274           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4275           /* Get a move list just to see the header, which
4276              will tell us whether this is really bug or zh */
4277           if (ics_getting_history == H_FALSE) {
4278             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4279             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280             SendToICS(str);
4281           }
4282     }
4283
4284     /* Take action if this is the first board of a new game, or of a
4285        different game than is currently being displayed.  */
4286     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4287         relation == RELATION_ISOLATED_BOARD) {
4288
4289         /* Forget the old game and get the history (if any) of the new one */
4290         if (gameMode != BeginningOfGame) {
4291           Reset(TRUE, TRUE);
4292         }
4293         newGame = TRUE;
4294         if (appData.autoRaiseBoard) BoardToTop();
4295         prevMove = -3;
4296         if (gamenum == -1) {
4297             newGameMode = IcsIdle;
4298         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4299                    appData.getMoveList && !reqFlag) {
4300             /* Need to get game history */
4301             ics_getting_history = H_REQUESTED;
4302             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4303             SendToICS(str);
4304         }
4305
4306         /* Initially flip the board to have black on the bottom if playing
4307            black or if the ICS flip flag is set, but let the user change
4308            it with the Flip View button. */
4309         flipView = appData.autoFlipView ?
4310           (newGameMode == IcsPlayingBlack) || ics_flip :
4311           appData.flipView;
4312
4313         /* Done with values from previous mode; copy in new ones */
4314         gameMode = newGameMode;
4315         ModeHighlight();
4316         ics_gamenum = gamenum;
4317         if (gamenum == gs_gamenum) {
4318             int klen = strlen(gs_kind);
4319             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4320             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4321             gameInfo.event = StrSave(str);
4322         } else {
4323             gameInfo.event = StrSave("ICS game");
4324         }
4325         gameInfo.site = StrSave(appData.icsHost);
4326         gameInfo.date = PGNDate();
4327         gameInfo.round = StrSave("-");
4328         gameInfo.white = StrSave(white);
4329         gameInfo.black = StrSave(black);
4330         timeControl = basetime * 60 * 1000;
4331         timeControl_2 = 0;
4332         timeIncrement = increment * 1000;
4333         movesPerSession = 0;
4334         gameInfo.timeControl = TimeControlTagValue();
4335         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4336   if (appData.debugMode) {
4337     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4338     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4339     setbuf(debugFP, NULL);
4340   }
4341
4342         gameInfo.outOfBook = NULL;
4343
4344         /* Do we have the ratings? */
4345         if (strcmp(player1Name, white) == 0 &&
4346             strcmp(player2Name, black) == 0) {
4347             if (appData.debugMode)
4348               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4349                       player1Rating, player2Rating);
4350             gameInfo.whiteRating = player1Rating;
4351             gameInfo.blackRating = player2Rating;
4352         } else if (strcmp(player2Name, white) == 0 &&
4353                    strcmp(player1Name, black) == 0) {
4354             if (appData.debugMode)
4355               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356                       player2Rating, player1Rating);
4357             gameInfo.whiteRating = player2Rating;
4358             gameInfo.blackRating = player1Rating;
4359         }
4360         player1Name[0] = player2Name[0] = NULLCHAR;
4361
4362         /* Silence shouts if requested */
4363         if (appData.quietPlay &&
4364             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4365             SendToICS(ics_prefix);
4366             SendToICS("set shout 0\n");
4367         }
4368     }
4369
4370     /* Deal with midgame name changes */
4371     if (!newGame) {
4372         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4373             if (gameInfo.white) free(gameInfo.white);
4374             gameInfo.white = StrSave(white);
4375         }
4376         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4377             if (gameInfo.black) free(gameInfo.black);
4378             gameInfo.black = StrSave(black);
4379         }
4380     }
4381
4382     /* Throw away game result if anything actually changes in examine mode */
4383     if (gameMode == IcsExamining && !newGame) {
4384         gameInfo.result = GameUnfinished;
4385         if (gameInfo.resultDetails != NULL) {
4386             free(gameInfo.resultDetails);
4387             gameInfo.resultDetails = NULL;
4388         }
4389     }
4390
4391     /* In pausing && IcsExamining mode, we ignore boards coming
4392        in if they are in a different variation than we are. */
4393     if (pauseExamInvalid) return;
4394     if (pausing && gameMode == IcsExamining) {
4395         if (moveNum <= pauseExamForwardMostMove) {
4396             pauseExamInvalid = TRUE;
4397             forwardMostMove = pauseExamForwardMostMove;
4398             return;
4399         }
4400     }
4401
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4404   }
4405     /* Parse the board */
4406     for (k = 0; k < ranks; k++) {
4407       for (j = 0; j < files; j++)
4408         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4409       if(gameInfo.holdingsWidth > 1) {
4410            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4411            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4412       }
4413     }
4414     CopyBoard(boards[moveNum], board);
4415     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4416     if (moveNum == 0) {
4417         startedFromSetupPosition =
4418           !CompareBoards(board, initialPosition);
4419         if(startedFromSetupPosition)
4420             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4421     }
4422
4423     /* [HGM] Set castling rights. Take the outermost Rooks,
4424        to make it also work for FRC opening positions. Note that board12
4425        is really defective for later FRC positions, as it has no way to
4426        indicate which Rook can castle if they are on the same side of King.
4427        For the initial position we grant rights to the outermost Rooks,
4428        and remember thos rights, and we then copy them on positions
4429        later in an FRC game. This means WB might not recognize castlings with
4430        Rooks that have moved back to their original position as illegal,
4431        but in ICS mode that is not its job anyway.
4432     */
4433     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4434     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4435
4436         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4437             if(board[0][i] == WhiteRook) j = i;
4438         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4439         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4440             if(board[0][i] == WhiteRook) j = i;
4441         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4443             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4444         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4446             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448
4449         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4450         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4451             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4452         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4453             if(board[BOARD_HEIGHT-1][k] == bKing)
4454                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4455         if(gameInfo.variant == VariantTwoKings) {
4456             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4457             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4458             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4459         }
4460     } else { int r;
4461         r = boards[moveNum][CASTLING][0] = initialRights[0];
4462         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4463         r = boards[moveNum][CASTLING][1] = initialRights[1];
4464         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4465         r = boards[moveNum][CASTLING][3] = initialRights[3];
4466         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4467         r = boards[moveNum][CASTLING][4] = initialRights[4];
4468         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4469         /* wildcastle kludge: always assume King has rights */
4470         r = boards[moveNum][CASTLING][2] = initialRights[2];
4471         r = boards[moveNum][CASTLING][5] = initialRights[5];
4472     }
4473     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4474     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4475
4476
4477     if (ics_getting_history == H_GOT_REQ_HEADER ||
4478         ics_getting_history == H_GOT_UNREQ_HEADER) {
4479         /* This was an initial position from a move list, not
4480            the current position */
4481         return;
4482     }
4483
4484     /* Update currentMove and known move number limits */
4485     newMove = newGame || moveNum > forwardMostMove;
4486
4487     if (newGame) {
4488         forwardMostMove = backwardMostMove = currentMove = moveNum;
4489         if (gameMode == IcsExamining && moveNum == 0) {
4490           /* Workaround for ICS limitation: we are not told the wild
4491              type when starting to examine a game.  But if we ask for
4492              the move list, the move list header will tell us */
4493             ics_getting_history = H_REQUESTED;
4494             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4495             SendToICS(str);
4496         }
4497     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4498                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4499 #if ZIPPY
4500         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4501         /* [HGM] applied this also to an engine that is silently watching        */
4502         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4503             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4504             gameInfo.variant == currentlyInitializedVariant) {
4505           takeback = forwardMostMove - moveNum;
4506           for (i = 0; i < takeback; i++) {
4507             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4508             SendToProgram("undo\n", &first);
4509           }
4510         }
4511 #endif
4512
4513         forwardMostMove = moveNum;
4514         if (!pausing || currentMove > forwardMostMove)
4515           currentMove = forwardMostMove;
4516     } else {
4517         /* New part of history that is not contiguous with old part */
4518         if (pausing && gameMode == IcsExamining) {
4519             pauseExamInvalid = TRUE;
4520             forwardMostMove = pauseExamForwardMostMove;
4521             return;
4522         }
4523         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4524 #if ZIPPY
4525             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4526                 // [HGM] when we will receive the move list we now request, it will be
4527                 // fed to the engine from the first move on. So if the engine is not
4528                 // in the initial position now, bring it there.
4529                 InitChessProgram(&first, 0);
4530             }
4531 #endif
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536         forwardMostMove = backwardMostMove = currentMove = moveNum;
4537     }
4538
4539     /* Update the clocks */
4540     if (strchr(elapsed_time, '.')) {
4541       /* Time is in ms */
4542       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4543       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4544     } else {
4545       /* Time is in seconds */
4546       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4547       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4548     }
4549
4550
4551 #if ZIPPY
4552     if (appData.zippyPlay && newGame &&
4553         gameMode != IcsObserving && gameMode != IcsIdle &&
4554         gameMode != IcsExamining)
4555       ZippyFirstBoard(moveNum, basetime, increment);
4556 #endif
4557
4558     /* Put the move on the move list, first converting
4559        to canonical algebraic form. */
4560     if (moveNum > 0) {
4561   if (appData.debugMode) {
4562     if (appData.debugMode) { int f = forwardMostMove;
4563         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4564                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4565                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4566     }
4567     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4568     fprintf(debugFP, "moveNum = %d\n", moveNum);
4569     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4570     setbuf(debugFP, NULL);
4571   }
4572         if (moveNum <= backwardMostMove) {
4573             /* We don't know what the board looked like before
4574                this move.  Punt. */
4575           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4576             strcat(parseList[moveNum - 1], " ");
4577             strcat(parseList[moveNum - 1], elapsed_time);
4578             moveList[moveNum - 1][0] = NULLCHAR;
4579         } else if (strcmp(move_str, "none") == 0) {
4580             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4581             /* Again, we don't know what the board looked like;
4582                this is really the start of the game. */
4583             parseList[moveNum - 1][0] = NULLCHAR;
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585             backwardMostMove = moveNum;
4586             startedFromSetupPosition = TRUE;
4587             fromX = fromY = toX = toY = -1;
4588         } else {
4589           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4590           //                 So we parse the long-algebraic move string in stead of the SAN move
4591           int valid; char buf[MSG_SIZ], *prom;
4592
4593           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4594                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4595           // str looks something like "Q/a1-a2"; kill the slash
4596           if(str[1] == '/')
4597             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4598           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4599           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4600                 strcat(buf, prom); // long move lacks promo specification!
4601           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4602                 if(appData.debugMode)
4603                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4604                 safeStrCpy(move_str, buf, MSG_SIZ);
4605           }
4606           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4607                                 &fromX, &fromY, &toX, &toY, &promoChar)
4608                || ParseOneMove(buf, moveNum - 1, &moveType,
4609                                 &fromX, &fromY, &toX, &toY, &promoChar);
4610           // end of long SAN patch
4611           if (valid) {
4612             (void) CoordsToAlgebraic(boards[moveNum - 1],
4613                                      PosFlags(moveNum - 1),
4614                                      fromY, fromX, toY, toX, promoChar,
4615                                      parseList[moveNum-1]);
4616             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4617               case MT_NONE:
4618               case MT_STALEMATE:
4619               default:
4620                 break;
4621               case MT_CHECK:
4622                 if(gameInfo.variant != VariantShogi)
4623                     strcat(parseList[moveNum - 1], "+");
4624                 break;
4625               case MT_CHECKMATE:
4626               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4627                 strcat(parseList[moveNum - 1], "#");
4628                 break;
4629             }
4630             strcat(parseList[moveNum - 1], " ");
4631             strcat(parseList[moveNum - 1], elapsed_time);
4632             /* currentMoveString is set as a side-effect of ParseOneMove */
4633             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4634             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4635             strcat(moveList[moveNum - 1], "\n");
4636
4637             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4638                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4639               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4640                 ChessSquare old, new = boards[moveNum][k][j];
4641                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4642                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4643                   if(old == new) continue;
4644                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4645                   else if(new == WhiteWazir || new == BlackWazir) {
4646                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4647                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4648                       else boards[moveNum][k][j] = old; // preserve type of Gold
4649                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4650                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4651               }
4652           } else {
4653             /* Move from ICS was illegal!?  Punt. */
4654             if (appData.debugMode) {
4655               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4656               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4657             }
4658             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662             fromX = fromY = toX = toY = -1;
4663           }
4664         }
4665   if (appData.debugMode) {
4666     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4667     setbuf(debugFP, NULL);
4668   }
4669
4670 #if ZIPPY
4671         /* Send move to chess program (BEFORE animating it). */
4672         if (appData.zippyPlay && !newGame && newMove &&
4673            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4674
4675             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4676                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4677                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4678                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4679                             move_str);
4680                     DisplayError(str, 0);
4681                 } else {
4682                     if (first.sendTime) {
4683                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4684                     }
4685                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4686                     if (firstMove && !bookHit) {
4687                         firstMove = FALSE;
4688                         if (first.useColors) {
4689                           SendToProgram(gameMode == IcsPlayingWhite ?
4690                                         "white\ngo\n" :
4691                                         "black\ngo\n", &first);
4692                         } else {
4693                           SendToProgram("go\n", &first);
4694                         }
4695                         first.maybeThinking = TRUE;
4696                     }
4697                 }
4698             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4699               if (moveList[moveNum - 1][0] == NULLCHAR) {
4700                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4701                 DisplayError(str, 0);
4702               } else {
4703                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4704                 SendMoveToProgram(moveNum - 1, &first);
4705               }
4706             }
4707         }
4708 #endif
4709     }
4710
4711     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4712         /* If move comes from a remote source, animate it.  If it
4713            isn't remote, it will have already been animated. */
4714         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4715             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4716         }
4717         if (!pausing && appData.highlightLastMove) {
4718             SetHighlights(fromX, fromY, toX, toY);
4719         }
4720     }
4721
4722     /* Start the clocks */
4723     whiteFlag = blackFlag = FALSE;
4724     appData.clockMode = !(basetime == 0 && increment == 0);
4725     if (ticking == 0) {
4726       ics_clock_paused = TRUE;
4727       StopClocks();
4728     } else if (ticking == 1) {
4729       ics_clock_paused = FALSE;
4730     }
4731     if (gameMode == IcsIdle ||
4732         relation == RELATION_OBSERVING_STATIC ||
4733         relation == RELATION_EXAMINING ||
4734         ics_clock_paused)
4735       DisplayBothClocks();
4736     else
4737       StartClocks();
4738
4739     /* Display opponents and material strengths */
4740     if (gameInfo.variant != VariantBughouse &&
4741         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4742         if (tinyLayout || smallLayout) {
4743             if(gameInfo.variant == VariantNormal)
4744               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4745                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4746                     basetime, increment);
4747             else
4748               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4749                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4750                     basetime, increment, (int) gameInfo.variant);
4751         } else {
4752             if(gameInfo.variant == VariantNormal)
4753               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment);
4756             else
4757               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4758                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4759                     basetime, increment, VariantName(gameInfo.variant));
4760         }
4761         DisplayTitle(str);
4762   if (appData.debugMode) {
4763     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4764   }
4765     }
4766
4767
4768     /* Display the board */
4769     if (!pausing && !appData.noGUI) {
4770
4771       if (appData.premove)
4772           if (!gotPremove ||
4773              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4774              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4775               ClearPremoveHighlights();
4776
4777       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4778         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4779       DrawPosition(j, boards[currentMove]);
4780
4781       DisplayMove(moveNum - 1);
4782       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4783             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4784               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4785         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4786       }
4787     }
4788
4789     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4790 #if ZIPPY
4791     if(bookHit) { // [HGM] book: simulate book reply
4792         static char bookMove[MSG_SIZ]; // a bit generous?
4793
4794         programStats.nodes = programStats.depth = programStats.time =
4795         programStats.score = programStats.got_only_move = 0;
4796         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4797
4798         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4799         strcat(bookMove, bookHit);
4800         HandleMachineMove(bookMove, &first);
4801     }
4802 #endif
4803 }
4804
4805 void
4806 GetMoveListEvent()
4807 {
4808     char buf[MSG_SIZ];
4809     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4810         ics_getting_history = H_REQUESTED;
4811         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4812         SendToICS(buf);
4813     }
4814 }
4815
4816 void
4817 AnalysisPeriodicEvent(force)
4818      int force;
4819 {
4820     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4821          && !force) || !appData.periodicUpdates)
4822       return;
4823
4824     /* Send . command to Crafty to collect stats */
4825     SendToProgram(".\n", &first);
4826
4827     /* Don't send another until we get a response (this makes
4828        us stop sending to old Crafty's which don't understand
4829        the "." command (sending illegal cmds resets node count & time,
4830        which looks bad)) */
4831     programStats.ok_to_send = 0;
4832 }
4833
4834 void ics_update_width(new_width)
4835         int new_width;
4836 {
4837         ics_printf("set width %d\n", new_width);
4838 }
4839
4840 void
4841 SendMoveToProgram(moveNum, cps)
4842      int moveNum;
4843      ChessProgramState *cps;
4844 {
4845     char buf[MSG_SIZ];
4846
4847     if (cps->useUsermove) {
4848       SendToProgram("usermove ", cps);
4849     }
4850     if (cps->useSAN) {
4851       char *space;
4852       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4853         int len = space - parseList[moveNum];
4854         memcpy(buf, parseList[moveNum], len);
4855         buf[len++] = '\n';
4856         buf[len] = NULLCHAR;
4857       } else {
4858         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4859       }
4860       SendToProgram(buf, cps);
4861     } else {
4862       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4863         AlphaRank(moveList[moveNum], 4);
4864         SendToProgram(moveList[moveNum], cps);
4865         AlphaRank(moveList[moveNum], 4); // and back
4866       } else
4867       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4868        * the engine. It would be nice to have a better way to identify castle
4869        * moves here. */
4870       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4871                                                                          && cps->useOOCastle) {
4872         int fromX = moveList[moveNum][0] - AAA;
4873         int fromY = moveList[moveNum][1] - ONE;
4874         int toX = moveList[moveNum][2] - AAA;
4875         int toY = moveList[moveNum][3] - ONE;
4876         if((boards[moveNum][fromY][fromX] == WhiteKing
4877             && boards[moveNum][toY][toX] == WhiteRook)
4878            || (boards[moveNum][fromY][fromX] == BlackKing
4879                && boards[moveNum][toY][toX] == BlackRook)) {
4880           if(toX > fromX) SendToProgram("O-O\n", cps);
4881           else SendToProgram("O-O-O\n", cps);
4882         }
4883         else SendToProgram(moveList[moveNum], cps);
4884       }
4885       else SendToProgram(moveList[moveNum], cps);
4886       /* End of additions by Tord */
4887     }
4888
4889     /* [HGM] setting up the opening has brought engine in force mode! */
4890     /*       Send 'go' if we are in a mode where machine should play. */
4891     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4892         (gameMode == TwoMachinesPlay   ||
4893 #if ZIPPY
4894          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4895 #endif
4896          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4897         SendToProgram("go\n", cps);
4898   if (appData.debugMode) {
4899     fprintf(debugFP, "(extra)\n");
4900   }
4901     }
4902     setboardSpoiledMachineBlack = 0;
4903 }
4904
4905 void
4906 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4907      ChessMove moveType;
4908      int fromX, fromY, toX, toY;
4909      char promoChar;
4910 {
4911     char user_move[MSG_SIZ];
4912
4913     switch (moveType) {
4914       default:
4915         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4916                 (int)moveType, fromX, fromY, toX, toY);
4917         DisplayError(user_move + strlen("say "), 0);
4918         break;
4919       case WhiteKingSideCastle:
4920       case BlackKingSideCastle:
4921       case WhiteQueenSideCastleWild:
4922       case BlackQueenSideCastleWild:
4923       /* PUSH Fabien */
4924       case WhiteHSideCastleFR:
4925       case BlackHSideCastleFR:
4926       /* POP Fabien */
4927         snprintf(user_move, MSG_SIZ, "o-o\n");
4928         break;
4929       case WhiteQueenSideCastle:
4930       case BlackQueenSideCastle:
4931       case WhiteKingSideCastleWild:
4932       case BlackKingSideCastleWild:
4933       /* PUSH Fabien */
4934       case WhiteASideCastleFR:
4935       case BlackASideCastleFR:
4936       /* POP Fabien */
4937         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4938         break;
4939       case WhiteNonPromotion:
4940       case BlackNonPromotion:
4941         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4942         break;
4943       case WhitePromotion:
4944       case BlackPromotion:
4945         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4946           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4947                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4948                 PieceToChar(WhiteFerz));
4949         else if(gameInfo.variant == VariantGreat)
4950           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4951                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4952                 PieceToChar(WhiteMan));
4953         else
4954           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4955                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4956                 promoChar);
4957         break;
4958       case WhiteDrop:
4959       case BlackDrop:
4960       drop:
4961         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4962                  ToUpper(PieceToChar((ChessSquare) fromX)),
4963                  AAA + toX, ONE + toY);
4964         break;
4965       case IllegalMove:  /* could be a variant we don't quite understand */
4966         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4967       case NormalMove:
4968       case WhiteCapturesEnPassant:
4969       case BlackCapturesEnPassant:
4970         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4971                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4972         break;
4973     }
4974     SendToICS(user_move);
4975     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4976         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4977 }
4978
4979 void
4980 UploadGameEvent()
4981 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4982     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4983     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4984     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4985         DisplayError("You cannot do this while you are playing or observing", 0);
4986         return;
4987     }
4988     if(gameMode != IcsExamining) { // is this ever not the case?
4989         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4990
4991         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4992           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4993         } else { // on FICS we must first go to general examine mode
4994           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4995         }
4996         if(gameInfo.variant != VariantNormal) {
4997             // try figure out wild number, as xboard names are not always valid on ICS
4998             for(i=1; i<=36; i++) {
4999               snprintf(buf, MSG_SIZ, "wild/%d", i);
5000                 if(StringToVariant(buf) == gameInfo.variant) break;
5001             }
5002             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5003             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5004             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5005         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5006         SendToICS(ics_prefix);
5007         SendToICS(buf);
5008         if(startedFromSetupPosition || backwardMostMove != 0) {
5009           fen = PositionToFEN(backwardMostMove, NULL);
5010           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5011             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5012             SendToICS(buf);
5013           } else { // FICS: everything has to set by separate bsetup commands
5014             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5015             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5016             SendToICS(buf);
5017             if(!WhiteOnMove(backwardMostMove)) {
5018                 SendToICS("bsetup tomove black\n");
5019             }
5020             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5021             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5022             SendToICS(buf);
5023             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5024             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5025             SendToICS(buf);
5026             i = boards[backwardMostMove][EP_STATUS];
5027             if(i >= 0) { // set e.p.
5028               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5029                 SendToICS(buf);
5030             }
5031             bsetup++;
5032           }
5033         }
5034       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5035             SendToICS("bsetup done\n"); // switch to normal examining.
5036     }
5037     for(i = backwardMostMove; i<last; i++) {
5038         char buf[20];
5039         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5040         SendToICS(buf);
5041     }
5042     SendToICS(ics_prefix);
5043     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5044 }
5045
5046 void
5047 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5048      int rf, ff, rt, ft;
5049      char promoChar;
5050      char move[7];
5051 {
5052     if (rf == DROP_RANK) {
5053       sprintf(move, "%c@%c%c\n",
5054                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5055     } else {
5056         if (promoChar == 'x' || promoChar == NULLCHAR) {
5057           sprintf(move, "%c%c%c%c\n",
5058                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5059         } else {
5060             sprintf(move, "%c%c%c%c%c\n",
5061                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5062         }
5063     }
5064 }
5065
5066 void
5067 ProcessICSInitScript(f)
5068      FILE *f;
5069 {
5070     char buf[MSG_SIZ];
5071
5072     while (fgets(buf, MSG_SIZ, f)) {
5073         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5074     }
5075
5076     fclose(f);
5077 }
5078
5079
5080 static int lastX, lastY, selectFlag, dragging;
5081
5082 void
5083 Sweep(int step)
5084 {
5085     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5086     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5087     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5088     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5089     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5090     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5091     do {
5092         promoSweep -= step;
5093         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5094         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5095         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5096         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5097         if(!step) step = 1;
5098     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5099             appData.testLegality && (promoSweep == king ||
5100             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5101     ChangeDragPiece(promoSweep);
5102 }
5103
5104 int PromoScroll(int x, int y)
5105 {
5106   int step = 0;
5107
5108   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5109   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5110   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5111   if(!step) return FALSE;
5112   lastX = x; lastY = y;
5113   if((promoSweep < BlackPawn) == flipView) step = -step;
5114   if(step > 0) selectFlag = 1;
5115   if(!selectFlag) Sweep(step);
5116   return FALSE;
5117 }
5118
5119 void
5120 NextPiece(int step)
5121 {
5122     ChessSquare piece = boards[currentMove][toY][toX];
5123     do {
5124         pieceSweep -= step;
5125         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5126         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5127         if(!step) step = -1;
5128     } while(PieceToChar(pieceSweep) == '.');
5129     boards[currentMove][toY][toX] = pieceSweep;
5130     DrawPosition(FALSE, boards[currentMove]);
5131     boards[currentMove][toY][toX] = piece;
5132 }
5133 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5134 void
5135 AlphaRank(char *move, int n)
5136 {
5137 //    char *p = move, c; int x, y;
5138
5139     if (appData.debugMode) {
5140         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5141     }
5142
5143     if(move[1]=='*' &&
5144        move[2]>='0' && move[2]<='9' &&
5145        move[3]>='a' && move[3]<='x'    ) {
5146         move[1] = '@';
5147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5149     } else
5150     if(move[0]>='0' && move[0]<='9' &&
5151        move[1]>='a' && move[1]<='x' &&
5152        move[2]>='0' && move[2]<='9' &&
5153        move[3]>='a' && move[3]<='x'    ) {
5154         /* input move, Shogi -> normal */
5155         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5156         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5157         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5158         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5159     } else
5160     if(move[1]=='@' &&
5161        move[3]>='0' && move[3]<='9' &&
5162        move[2]>='a' && move[2]<='x'    ) {
5163         move[1] = '*';
5164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5166     } else
5167     if(
5168        move[0]>='a' && move[0]<='x' &&
5169        move[3]>='0' && move[3]<='9' &&
5170        move[2]>='a' && move[2]<='x'    ) {
5171          /* output move, normal -> Shogi */
5172         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5173         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5174         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5175         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5176         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5177     }
5178     if (appData.debugMode) {
5179         fprintf(debugFP, "   out = '%s'\n", move);
5180     }
5181 }
5182
5183 char yy_textstr[8000];
5184
5185 /* Parser for moves from gnuchess, ICS, or user typein box */
5186 Boolean
5187 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5188      char *move;
5189      int moveNum;
5190      ChessMove *moveType;
5191      int *fromX, *fromY, *toX, *toY;
5192      char *promoChar;
5193 {
5194     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5195
5196     switch (*moveType) {
5197       case WhitePromotion:
5198       case BlackPromotion:
5199       case WhiteNonPromotion:
5200       case BlackNonPromotion:
5201       case NormalMove:
5202       case WhiteCapturesEnPassant:
5203       case BlackCapturesEnPassant:
5204       case WhiteKingSideCastle:
5205       case WhiteQueenSideCastle:
5206       case BlackKingSideCastle:
5207       case BlackQueenSideCastle:
5208       case WhiteKingSideCastleWild:
5209       case WhiteQueenSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* Code added by Tord: */
5213       case WhiteHSideCastleFR:
5214       case WhiteASideCastleFR:
5215       case BlackHSideCastleFR:
5216       case BlackASideCastleFR:
5217       /* End of code added by Tord */
5218       case IllegalMove:         /* bug or odd chess variant */
5219         *fromX = currentMoveString[0] - AAA;
5220         *fromY = currentMoveString[1] - ONE;
5221         *toX = currentMoveString[2] - AAA;
5222         *toY = currentMoveString[3] - ONE;
5223         *promoChar = currentMoveString[4];
5224         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5225             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5226     if (appData.debugMode) {
5227         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5228     }
5229             *fromX = *fromY = *toX = *toY = 0;
5230             return FALSE;
5231         }
5232         if (appData.testLegality) {
5233           return (*moveType != IllegalMove);
5234         } else {
5235           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5236                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5237         }
5238
5239       case WhiteDrop:
5240       case BlackDrop:
5241         *fromX = *moveType == WhiteDrop ?
5242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5243           (int) CharToPiece(ToLower(currentMoveString[0]));
5244         *fromY = DROP_RANK;
5245         *toX = currentMoveString[2] - AAA;
5246         *toY = currentMoveString[3] - ONE;
5247         *promoChar = NULLCHAR;
5248         return TRUE;
5249
5250       case AmbiguousMove:
5251       case ImpossibleMove:
5252       case EndOfFile:
5253       case ElapsedTime:
5254       case Comment:
5255       case PGNTag:
5256       case NAG:
5257       case WhiteWins:
5258       case BlackWins:
5259       case GameIsDrawn:
5260       default:
5261     if (appData.debugMode) {
5262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5263     }
5264         /* bug? */
5265         *fromX = *fromY = *toX = *toY = 0;
5266         *promoChar = NULLCHAR;
5267         return FALSE;
5268     }
5269 }
5270
5271 Boolean pushed = FALSE;
5272
5273 void
5274 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5275 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5276   int fromX, fromY, toX, toY; char promoChar;
5277   ChessMove moveType;
5278   Boolean valid;
5279   int nr = 0;
5280
5281   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5282     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5283     pushed = TRUE;
5284   }
5285   endPV = forwardMostMove;
5286   do {
5287     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5288     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5289     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5290 if(appData.debugMode){
5291 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);
5292 }
5293     if(!valid && nr == 0 &&
5294        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5295         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5296         // Hande case where played move is different from leading PV move
5297         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5298         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5299         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5300         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5301           endPV += 2; // if position different, keep this
5302           moveList[endPV-1][0] = fromX + AAA;
5303           moveList[endPV-1][1] = fromY + ONE;
5304           moveList[endPV-1][2] = toX + AAA;
5305           moveList[endPV-1][3] = toY + ONE;
5306           parseList[endPV-1][0] = NULLCHAR;
5307           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5308         }
5309       }
5310     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5311     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5312     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5313     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5314         valid++; // allow comments in PV
5315         continue;
5316     }
5317     nr++;
5318     if(endPV+1 > framePtr) break; // no space, truncate
5319     if(!valid) break;
5320     endPV++;
5321     CopyBoard(boards[endPV], boards[endPV-1]);
5322     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5323     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5324     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5325     CoordsToAlgebraic(boards[endPV - 1],
5326                              PosFlags(endPV - 1),
5327                              fromY, fromX, toY, toX, promoChar,
5328                              parseList[endPV - 1]);
5329   } while(valid);
5330   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5331   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5332   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5333                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5334   DrawPosition(TRUE, boards[currentMove]);
5335 }
5336
5337 int
5338 MultiPV(ChessProgramState *cps)
5339 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5340         int i;
5341         for(i=0; i<cps->nrOptions; i++)
5342             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5343                 return i;
5344         return -1;
5345 }
5346
5347 Boolean
5348 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5349 {
5350         int startPV, multi, lineStart, origIndex = index;
5351         char *p, buf2[MSG_SIZ];
5352
5353         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5354         lastX = x; lastY = y;
5355         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5356         lineStart = startPV = index;
5357         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5358         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5359         index = startPV;
5360         do{ while(buf[index] && buf[index] != '\n') index++;
5361         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5362         buf[index] = 0;
5363         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5364                 int n = first.option[multi].value;
5365                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5366                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5367                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5368                 first.option[multi].value = n;
5369                 *start = *end = 0;
5370                 return FALSE;
5371         }
5372         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5373         *start = startPV; *end = index-1;
5374         return TRUE;
5375 }
5376
5377 Boolean
5378 LoadPV(int x, int y)
5379 { // called on right mouse click to load PV
5380   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5381   lastX = x; lastY = y;
5382   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5383   return TRUE;
5384 }
5385
5386 void
5387 UnLoadPV()
5388 {
5389   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5390   if(endPV < 0) return;
5391   endPV = -1;
5392   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5393         Boolean saveAnimate = appData.animate;
5394         if(pushed) {
5395             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5396                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5397             } else storedGames--; // abandon shelved tail of original game
5398         }
5399         pushed = FALSE;
5400         forwardMostMove = currentMove;
5401         currentMove = oldFMM;
5402         appData.animate = FALSE;
5403         ToNrEvent(forwardMostMove);
5404         appData.animate = saveAnimate;
5405   }
5406   currentMove = forwardMostMove;
5407   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5408   ClearPremoveHighlights();
5409   DrawPosition(TRUE, boards[currentMove]);
5410 }
5411
5412 void
5413 MovePV(int x, int y, int h)
5414 { // step through PV based on mouse coordinates (called on mouse move)
5415   int margin = h>>3, step = 0;
5416
5417   // we must somehow check if right button is still down (might be released off board!)
5418   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5419   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5420   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5421   if(!step) return;
5422   lastX = x; lastY = y;
5423
5424   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5425   if(endPV < 0) return;
5426   if(y < margin) step = 1; else
5427   if(y > h - margin) step = -1;
5428   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5429   currentMove += step;
5430   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5431   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5432                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5433   DrawPosition(FALSE, boards[currentMove]);
5434 }
5435
5436
5437 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5438 // All positions will have equal probability, but the current method will not provide a unique
5439 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5440 #define DARK 1
5441 #define LITE 2
5442 #define ANY 3
5443
5444 int squaresLeft[4];
5445 int piecesLeft[(int)BlackPawn];
5446 int seed, nrOfShuffles;
5447
5448 void GetPositionNumber()
5449 {       // sets global variable seed
5450         int i;
5451
5452         seed = appData.defaultFrcPosition;
5453         if(seed < 0) { // randomize based on time for negative FRC position numbers
5454                 for(i=0; i<50; i++) seed += random();
5455                 seed = random() ^ random() >> 8 ^ random() << 8;
5456                 if(seed<0) seed = -seed;
5457         }
5458 }
5459
5460 int put(Board board, int pieceType, int rank, int n, int shade)
5461 // put the piece on the (n-1)-th empty squares of the given shade
5462 {
5463         int i;
5464
5465         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5466                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5467                         board[rank][i] = (ChessSquare) pieceType;
5468                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5469                         squaresLeft[ANY]--;
5470                         piecesLeft[pieceType]--;
5471                         return i;
5472                 }
5473         }
5474         return -1;
5475 }
5476
5477
5478 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5479 // calculate where the next piece goes, (any empty square), and put it there
5480 {
5481         int i;
5482
5483         i = seed % squaresLeft[shade];
5484         nrOfShuffles *= squaresLeft[shade];
5485         seed /= squaresLeft[shade];
5486         put(board, pieceType, rank, i, shade);
5487 }
5488
5489 void AddTwoPieces(Board board, int pieceType, int rank)
5490 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5491 {
5492         int i, n=squaresLeft[ANY], j=n-1, k;
5493
5494         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5495         i = seed % k;  // pick one
5496         nrOfShuffles *= k;
5497         seed /= k;
5498         while(i >= j) i -= j--;
5499         j = n - 1 - j; i += j;
5500         put(board, pieceType, rank, j, ANY);
5501         put(board, pieceType, rank, i, ANY);
5502 }
5503
5504 void SetUpShuffle(Board board, int number)
5505 {
5506         int i, p, first=1;
5507
5508         GetPositionNumber(); nrOfShuffles = 1;
5509
5510         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5511         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5512         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5513
5514         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5515
5516         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5517             p = (int) board[0][i];
5518             if(p < (int) BlackPawn) piecesLeft[p] ++;
5519             board[0][i] = EmptySquare;
5520         }
5521
5522         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5523             // shuffles restricted to allow normal castling put KRR first
5524             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5525                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5526             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5527                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5528             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5529                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5530             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5531                 put(board, WhiteRook, 0, 0, ANY);
5532             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5533         }
5534
5535         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5536             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5537             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5538                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5539                 while(piecesLeft[p] >= 2) {
5540                     AddOnePiece(board, p, 0, LITE);
5541                     AddOnePiece(board, p, 0, DARK);
5542                 }
5543                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5544             }
5545
5546         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5547             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5548             // but we leave King and Rooks for last, to possibly obey FRC restriction
5549             if(p == (int)WhiteRook) continue;
5550             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5551             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5552         }
5553
5554         // now everything is placed, except perhaps King (Unicorn) and Rooks
5555
5556         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5557             // Last King gets castling rights
5558             while(piecesLeft[(int)WhiteUnicorn]) {
5559                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5560                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5561             }
5562
5563             while(piecesLeft[(int)WhiteKing]) {
5564                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5566             }
5567
5568
5569         } else {
5570             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5571             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5572         }
5573
5574         // Only Rooks can be left; simply place them all
5575         while(piecesLeft[(int)WhiteRook]) {
5576                 i = put(board, WhiteRook, 0, 0, ANY);
5577                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5578                         if(first) {
5579                                 first=0;
5580                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5581                         }
5582                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5583                 }
5584         }
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5586             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5587         }
5588
5589         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5590 }
5591
5592 int SetCharTable( char *table, const char * map )
5593 /* [HGM] moved here from winboard.c because of its general usefulness */
5594 /*       Basically a safe strcpy that uses the last character as King */
5595 {
5596     int result = FALSE; int NrPieces;
5597
5598     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5599                     && NrPieces >= 12 && !(NrPieces&1)) {
5600         int i; /* [HGM] Accept even length from 12 to 34 */
5601
5602         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5603         for( i=0; i<NrPieces/2-1; i++ ) {
5604             table[i] = map[i];
5605             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5606         }
5607         table[(int) WhiteKing]  = map[NrPieces/2-1];
5608         table[(int) BlackKing]  = map[NrPieces-1];
5609
5610         result = TRUE;
5611     }
5612
5613     return result;
5614 }
5615
5616 void Prelude(Board board)
5617 {       // [HGM] superchess: random selection of exo-pieces
5618         int i, j, k; ChessSquare p;
5619         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5620
5621         GetPositionNumber(); // use FRC position number
5622
5623         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5624             SetCharTable(pieceToChar, appData.pieceToCharTable);
5625             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5626                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5627         }
5628
5629         j = seed%4;                 seed /= 4;
5630         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5631         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5632         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5633         j = seed%3 + (seed%3 >= j); seed /= 3;
5634         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5635         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5636         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5637         j = seed%3;                 seed /= 3;
5638         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5639         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5640         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5641         j = seed%2 + (seed%2 >= j); seed /= 2;
5642         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5643         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5644         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5645         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5646         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5647         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5648         put(board, exoPieces[0],    0, 0, ANY);
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5650 }
5651
5652 void
5653 InitPosition(redraw)
5654      int redraw;
5655 {
5656     ChessSquare (* pieces)[BOARD_FILES];
5657     int i, j, pawnRow, overrule,
5658     oldx = gameInfo.boardWidth,
5659     oldy = gameInfo.boardHeight,
5660     oldh = gameInfo.holdingsWidth;
5661     static int oldv;
5662
5663     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5664
5665     /* [AS] Initialize pv info list [HGM] and game status */
5666     {
5667         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5668             pvInfoList[i].depth = 0;
5669             boards[i][EP_STATUS] = EP_NONE;
5670             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5671         }
5672
5673         initialRulePlies = 0; /* 50-move counter start */
5674
5675         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5676         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5677     }
5678
5679
5680     /* [HGM] logic here is completely changed. In stead of full positions */
5681     /* the initialized data only consist of the two backranks. The switch */
5682     /* selects which one we will use, which is than copied to the Board   */
5683     /* initialPosition, which for the rest is initialized by Pawns and    */
5684     /* empty squares. This initial position is then copied to boards[0],  */
5685     /* possibly after shuffling, so that it remains available.            */
5686
5687     gameInfo.holdingsWidth = 0; /* default board sizes */
5688     gameInfo.boardWidth    = 8;
5689     gameInfo.boardHeight   = 8;
5690     gameInfo.holdingsSize  = 0;
5691     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5692     for(i=0; i<BOARD_FILES-2; i++)
5693       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5694     initialPosition[EP_STATUS] = EP_NONE;
5695     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5696     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5697          SetCharTable(pieceNickName, appData.pieceNickNames);
5698     else SetCharTable(pieceNickName, "............");
5699     pieces = FIDEArray;
5700
5701     switch (gameInfo.variant) {
5702     case VariantFischeRandom:
5703       shuffleOpenings = TRUE;
5704     default:
5705       break;
5706     case VariantShatranj:
5707       pieces = ShatranjArray;
5708       nrCastlingRights = 0;
5709       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5710       break;
5711     case VariantMakruk:
5712       pieces = makrukArray;
5713       nrCastlingRights = 0;
5714       startedFromSetupPosition = TRUE;
5715       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5716       break;
5717     case VariantTwoKings:
5718       pieces = twoKingsArray;
5719       break;
5720     case VariantCapaRandom:
5721       shuffleOpenings = TRUE;
5722     case VariantCapablanca:
5723       pieces = CapablancaArray;
5724       gameInfo.boardWidth = 10;
5725       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5726       break;
5727     case VariantGothic:
5728       pieces = GothicArray;
5729       gameInfo.boardWidth = 10;
5730       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5731       break;
5732     case VariantSChess:
5733       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5734       gameInfo.holdingsSize = 7;
5735       break;
5736     case VariantJanus:
5737       pieces = JanusArray;
5738       gameInfo.boardWidth = 10;
5739       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5740       nrCastlingRights = 6;
5741         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5742         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5743         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5744         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5745         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5746         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5747       break;
5748     case VariantFalcon:
5749       pieces = FalconArray;
5750       gameInfo.boardWidth = 10;
5751       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5752       break;
5753     case VariantXiangqi:
5754       pieces = XiangqiArray;
5755       gameInfo.boardWidth  = 9;
5756       gameInfo.boardHeight = 10;
5757       nrCastlingRights = 0;
5758       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5759       break;
5760     case VariantShogi:
5761       pieces = ShogiArray;
5762       gameInfo.boardWidth  = 9;
5763       gameInfo.boardHeight = 9;
5764       gameInfo.holdingsSize = 7;
5765       nrCastlingRights = 0;
5766       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5767       break;
5768     case VariantCourier:
5769       pieces = CourierArray;
5770       gameInfo.boardWidth  = 12;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5773       break;
5774     case VariantKnightmate:
5775       pieces = KnightmateArray;
5776       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5777       break;
5778     case VariantSpartan:
5779       pieces = SpartanArray;
5780       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5781       break;
5782     case VariantFairy:
5783       pieces = fairyArray;
5784       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5785       break;
5786     case VariantGreat:
5787       pieces = GreatArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5790       gameInfo.holdingsSize = 8;
5791       break;
5792     case VariantSuper:
5793       pieces = FIDEArray;
5794       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5795       gameInfo.holdingsSize = 8;
5796       startedFromSetupPosition = TRUE;
5797       break;
5798     case VariantCrazyhouse:
5799     case VariantBughouse:
5800       pieces = FIDEArray;
5801       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5802       gameInfo.holdingsSize = 5;
5803       break;
5804     case VariantWildCastle:
5805       pieces = FIDEArray;
5806       /* !!?shuffle with kings guaranteed to be on d or e file */
5807       shuffleOpenings = 1;
5808       break;
5809     case VariantNoCastle:
5810       pieces = FIDEArray;
5811       nrCastlingRights = 0;
5812       /* !!?unconstrained back-rank shuffle */
5813       shuffleOpenings = 1;
5814       break;
5815     }
5816
5817     overrule = 0;
5818     if(appData.NrFiles >= 0) {
5819         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5820         gameInfo.boardWidth = appData.NrFiles;
5821     }
5822     if(appData.NrRanks >= 0) {
5823         gameInfo.boardHeight = appData.NrRanks;
5824     }
5825     if(appData.holdingsSize >= 0) {
5826         i = appData.holdingsSize;
5827         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5828         gameInfo.holdingsSize = i;
5829     }
5830     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5831     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5832         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5833
5834     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5835     if(pawnRow < 1) pawnRow = 1;
5836     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5837
5838     /* User pieceToChar list overrules defaults */
5839     if(appData.pieceToCharTable != NULL)
5840         SetCharTable(pieceToChar, appData.pieceToCharTable);
5841
5842     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5843
5844         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5845             s = (ChessSquare) 0; /* account holding counts in guard band */
5846         for( i=0; i<BOARD_HEIGHT; i++ )
5847             initialPosition[i][j] = s;
5848
5849         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5850         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5851         initialPosition[pawnRow][j] = WhitePawn;
5852         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5853         if(gameInfo.variant == VariantXiangqi) {
5854             if(j&1) {
5855                 initialPosition[pawnRow][j] =
5856                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5857                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5858                    initialPosition[2][j] = WhiteCannon;
5859                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5860                 }
5861             }
5862         }
5863         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5864     }
5865     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5866
5867             j=BOARD_LEFT+1;
5868             initialPosition[1][j] = WhiteBishop;
5869             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5870             j=BOARD_RGHT-2;
5871             initialPosition[1][j] = WhiteRook;
5872             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5873     }
5874
5875     if( nrCastlingRights == -1) {
5876         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5877         /*       This sets default castling rights from none to normal corners   */
5878         /* Variants with other castling rights must set them themselves above    */
5879         nrCastlingRights = 6;
5880
5881         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5884         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5887      }
5888
5889      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5890      if(gameInfo.variant == VariantGreat) { // promotion commoners
5891         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5892         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5893         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5894         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5895      }
5896      if( gameInfo.variant == VariantSChess ) {
5897       initialPosition[1][0] = BlackMarshall;
5898       initialPosition[2][0] = BlackAngel;
5899       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5900       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5901       initialPosition[1][1] = initialPosition[2][1] = 
5902       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5903      }
5904   if (appData.debugMode) {
5905     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5906   }
5907     if(shuffleOpenings) {
5908         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5909         startedFromSetupPosition = TRUE;
5910     }
5911     if(startedFromPositionFile) {
5912       /* [HGM] loadPos: use PositionFile for every new game */
5913       CopyBoard(initialPosition, filePosition);
5914       for(i=0; i<nrCastlingRights; i++)
5915           initialRights[i] = filePosition[CASTLING][i];
5916       startedFromSetupPosition = TRUE;
5917     }
5918
5919     CopyBoard(boards[0], initialPosition);
5920
5921     if(oldx != gameInfo.boardWidth ||
5922        oldy != gameInfo.boardHeight ||
5923        oldv != gameInfo.variant ||
5924        oldh != gameInfo.holdingsWidth
5925                                          )
5926             InitDrawingSizes(-2 ,0);
5927
5928     oldv = gameInfo.variant;
5929     if (redraw)
5930       DrawPosition(TRUE, boards[currentMove]);
5931 }
5932
5933 void
5934 SendBoard(cps, moveNum)
5935      ChessProgramState *cps;
5936      int moveNum;
5937 {
5938     char message[MSG_SIZ];
5939
5940     if (cps->useSetboard) {
5941       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5942       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5943       SendToProgram(message, cps);
5944       free(fen);
5945
5946     } else {
5947       ChessSquare *bp;
5948       int i, j;
5949       /* Kludge to set black to move, avoiding the troublesome and now
5950        * deprecated "black" command.
5951        */
5952       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5953         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5954
5955       SendToProgram("edit\n", cps);
5956       SendToProgram("#\n", cps);
5957       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5958         bp = &boards[moveNum][i][BOARD_LEFT];
5959         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5960           if ((int) *bp < (int) BlackPawn) {
5961             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5962                     AAA + j, ONE + i);
5963             if(message[0] == '+' || message[0] == '~') {
5964               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5965                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5966                         AAA + j, ONE + i);
5967             }
5968             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5969                 message[1] = BOARD_RGHT   - 1 - j + '1';
5970                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5971             }
5972             SendToProgram(message, cps);
5973           }
5974         }
5975       }
5976
5977       SendToProgram("c\n", cps);
5978       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5979         bp = &boards[moveNum][i][BOARD_LEFT];
5980         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5981           if (((int) *bp != (int) EmptySquare)
5982               && ((int) *bp >= (int) BlackPawn)) {
5983             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5984                     AAA + j, ONE + i);
5985             if(message[0] == '+' || message[0] == '~') {
5986               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5987                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5988                         AAA + j, ONE + i);
5989             }
5990             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5991                 message[1] = BOARD_RGHT   - 1 - j + '1';
5992                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5993             }
5994             SendToProgram(message, cps);
5995           }
5996         }
5997       }
5998
5999       SendToProgram(".\n", cps);
6000     }
6001     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6002 }
6003
6004 ChessSquare
6005 DefaultPromoChoice(int white)
6006 {
6007     ChessSquare result;
6008     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6009         result = WhiteFerz; // no choice
6010     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6011         result= WhiteKing; // in Suicide Q is the last thing we want
6012     else if(gameInfo.variant == VariantSpartan)
6013         result = white ? WhiteQueen : WhiteAngel;
6014     else result = WhiteQueen;
6015     if(!white) result = WHITE_TO_BLACK result;
6016     return result;
6017 }
6018
6019 static int autoQueen; // [HGM] oneclick
6020
6021 int
6022 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6023 {
6024     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6025     /* [HGM] add Shogi promotions */
6026     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6027     ChessSquare piece;
6028     ChessMove moveType;
6029     Boolean premove;
6030
6031     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6032     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6033
6034     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6035       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6036         return FALSE;
6037
6038     piece = boards[currentMove][fromY][fromX];
6039     if(gameInfo.variant == VariantShogi) {
6040         promotionZoneSize = BOARD_HEIGHT/3;
6041         highestPromotingPiece = (int)WhiteFerz;
6042     } else if(gameInfo.variant == VariantMakruk) {
6043         promotionZoneSize = 3;
6044     }
6045
6046     // Treat Lance as Pawn when it is not representing Amazon
6047     if(gameInfo.variant != VariantSuper) {
6048         if(piece == WhiteLance) piece = WhitePawn; else
6049         if(piece == BlackLance) piece = BlackPawn;
6050     }
6051
6052     // next weed out all moves that do not touch the promotion zone at all
6053     if((int)piece >= BlackPawn) {
6054         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6055              return FALSE;
6056         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6057     } else {
6058         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6059            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6060     }
6061
6062     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6063
6064     // weed out mandatory Shogi promotions
6065     if(gameInfo.variant == VariantShogi) {
6066         if(piece >= BlackPawn) {
6067             if(toY == 0 && piece == BlackPawn ||
6068                toY == 0 && piece == BlackQueen ||
6069                toY <= 1 && piece == BlackKnight) {
6070                 *promoChoice = '+';
6071                 return FALSE;
6072             }
6073         } else {
6074             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6075                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6076                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6077                 *promoChoice = '+';
6078                 return FALSE;
6079             }
6080         }
6081     }
6082
6083     // weed out obviously illegal Pawn moves
6084     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6085         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6086         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6087         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6088         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6089         // note we are not allowed to test for valid (non-)capture, due to premove
6090     }
6091
6092     // we either have a choice what to promote to, or (in Shogi) whether to promote
6093     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6094         *promoChoice = PieceToChar(BlackFerz);  // no choice
6095         return FALSE;
6096     }
6097     // no sense asking what we must promote to if it is going to explode...
6098     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6099         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6100         return FALSE;
6101     }
6102     // give caller the default choice even if we will not make it
6103     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6104     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6105     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6106                            && gameInfo.variant != VariantShogi
6107                            && gameInfo.variant != VariantSuper) return FALSE;
6108     if(autoQueen) return FALSE; // predetermined
6109
6110     // suppress promotion popup on illegal moves that are not premoves
6111     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6112               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6113     if(appData.testLegality && !premove) {
6114         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6115                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6116         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6117             return FALSE;
6118     }
6119
6120     return TRUE;
6121 }
6122
6123 int
6124 InPalace(row, column)
6125      int row, column;
6126 {   /* [HGM] for Xiangqi */
6127     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6128          column < (BOARD_WIDTH + 4)/2 &&
6129          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6130     return FALSE;
6131 }
6132
6133 int
6134 PieceForSquare (x, y)
6135      int x;
6136      int y;
6137 {
6138   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6139      return -1;
6140   else
6141      return boards[currentMove][y][x];
6142 }
6143
6144 int
6145 OKToStartUserMove(x, y)
6146      int x, y;
6147 {
6148     ChessSquare from_piece;
6149     int white_piece;
6150
6151     if (matchMode) return FALSE;
6152     if (gameMode == EditPosition) return TRUE;
6153
6154     if (x >= 0 && y >= 0)
6155       from_piece = boards[currentMove][y][x];
6156     else
6157       from_piece = EmptySquare;
6158
6159     if (from_piece == EmptySquare) return FALSE;
6160
6161     white_piece = (int)from_piece >= (int)WhitePawn &&
6162       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6163
6164     switch (gameMode) {
6165       case PlayFromGameFile:
6166       case AnalyzeFile:
6167       case TwoMachinesPlay:
6168       case EndOfGame:
6169         return FALSE;
6170
6171       case IcsObserving:
6172       case IcsIdle:
6173         return FALSE;
6174
6175       case MachinePlaysWhite:
6176       case IcsPlayingBlack:
6177         if (appData.zippyPlay) return FALSE;
6178         if (white_piece) {
6179             DisplayMoveError(_("You are playing Black"));
6180             return FALSE;
6181         }
6182         break;
6183
6184       case MachinePlaysBlack:
6185       case IcsPlayingWhite:
6186         if (appData.zippyPlay) return FALSE;
6187         if (!white_piece) {
6188             DisplayMoveError(_("You are playing White"));
6189             return FALSE;
6190         }
6191         break;
6192
6193       case EditGame:
6194         if (!white_piece && WhiteOnMove(currentMove)) {
6195             DisplayMoveError(_("It is White's turn"));
6196             return FALSE;
6197         }
6198         if (white_piece && !WhiteOnMove(currentMove)) {
6199             DisplayMoveError(_("It is Black's turn"));
6200             return FALSE;
6201         }
6202         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6203             /* Editing correspondence game history */
6204             /* Could disallow this or prompt for confirmation */
6205             cmailOldMove = -1;
6206         }
6207         break;
6208
6209       case BeginningOfGame:
6210         if (appData.icsActive) return FALSE;
6211         if (!appData.noChessProgram) {
6212             if (!white_piece) {
6213                 DisplayMoveError(_("You are playing White"));
6214                 return FALSE;
6215             }
6216         }
6217         break;
6218
6219       case Training:
6220         if (!white_piece && WhiteOnMove(currentMove)) {
6221             DisplayMoveError(_("It is White's turn"));
6222             return FALSE;
6223         }
6224         if (white_piece && !WhiteOnMove(currentMove)) {
6225             DisplayMoveError(_("It is Black's turn"));
6226             return FALSE;
6227         }
6228         break;
6229
6230       default:
6231       case IcsExamining:
6232         break;
6233     }
6234     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6235         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6236         && gameMode != AnalyzeFile && gameMode != Training) {
6237         DisplayMoveError(_("Displayed position is not current"));
6238         return FALSE;
6239     }
6240     return TRUE;
6241 }
6242
6243 Boolean
6244 OnlyMove(int *x, int *y, Boolean captures) {
6245     DisambiguateClosure cl;
6246     if (appData.zippyPlay) return FALSE;
6247     switch(gameMode) {
6248       case MachinePlaysBlack:
6249       case IcsPlayingWhite:
6250       case BeginningOfGame:
6251         if(!WhiteOnMove(currentMove)) return FALSE;
6252         break;
6253       case MachinePlaysWhite:
6254       case IcsPlayingBlack:
6255         if(WhiteOnMove(currentMove)) return FALSE;
6256         break;
6257       case EditGame:
6258         break;
6259       default:
6260         return FALSE;
6261     }
6262     cl.pieceIn = EmptySquare;
6263     cl.rfIn = *y;
6264     cl.ffIn = *x;
6265     cl.rtIn = -1;
6266     cl.ftIn = -1;
6267     cl.promoCharIn = NULLCHAR;
6268     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6269     if( cl.kind == NormalMove ||
6270         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6271         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6272         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6273       fromX = cl.ff;
6274       fromY = cl.rf;
6275       *x = cl.ft;
6276       *y = cl.rt;
6277       return TRUE;
6278     }
6279     if(cl.kind != ImpossibleMove) return FALSE;
6280     cl.pieceIn = EmptySquare;
6281     cl.rfIn = -1;
6282     cl.ffIn = -1;
6283     cl.rtIn = *y;
6284     cl.ftIn = *x;
6285     cl.promoCharIn = NULLCHAR;
6286     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6287     if( cl.kind == NormalMove ||
6288         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6289         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6290         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6291       fromX = cl.ff;
6292       fromY = cl.rf;
6293       *x = cl.ft;
6294       *y = cl.rt;
6295       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6296       return TRUE;
6297     }
6298     return FALSE;
6299 }
6300
6301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6303 int lastLoadGameUseList = FALSE;
6304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6305 ChessMove lastLoadGameStart = EndOfFile;
6306
6307 void
6308 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6309      int fromX, fromY, toX, toY;
6310      int promoChar;
6311 {
6312     ChessMove moveType;
6313     ChessSquare pdown, pup;
6314
6315     /* Check if the user is playing in turn.  This is complicated because we
6316        let the user "pick up" a piece before it is his turn.  So the piece he
6317        tried to pick up may have been captured by the time he puts it down!
6318        Therefore we use the color the user is supposed to be playing in this
6319        test, not the color of the piece that is currently on the starting
6320        square---except in EditGame mode, where the user is playing both
6321        sides; fortunately there the capture race can't happen.  (It can
6322        now happen in IcsExamining mode, but that's just too bad.  The user
6323        will get a somewhat confusing message in that case.)
6324        */
6325
6326     switch (gameMode) {
6327       case PlayFromGameFile:
6328       case AnalyzeFile:
6329       case TwoMachinesPlay:
6330       case EndOfGame:
6331       case IcsObserving:
6332       case IcsIdle:
6333         /* We switched into a game mode where moves are not accepted,
6334            perhaps while the mouse button was down. */
6335         return;
6336
6337       case MachinePlaysWhite:
6338         /* User is moving for Black */
6339         if (WhiteOnMove(currentMove)) {
6340             DisplayMoveError(_("It is White's turn"));
6341             return;
6342         }
6343         break;
6344
6345       case MachinePlaysBlack:
6346         /* User is moving for White */
6347         if (!WhiteOnMove(currentMove)) {
6348             DisplayMoveError(_("It is Black's turn"));
6349             return;
6350         }
6351         break;
6352
6353       case EditGame:
6354       case IcsExamining:
6355       case BeginningOfGame:
6356       case AnalyzeMode:
6357       case Training:
6358         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6361             /* User is moving for Black */
6362             if (WhiteOnMove(currentMove)) {
6363                 DisplayMoveError(_("It is White's turn"));
6364                 return;
6365             }
6366         } else {
6367             /* User is moving for White */
6368             if (!WhiteOnMove(currentMove)) {
6369                 DisplayMoveError(_("It is Black's turn"));
6370                 return;
6371             }
6372         }
6373         break;
6374
6375       case IcsPlayingBlack:
6376         /* User is moving for Black */
6377         if (WhiteOnMove(currentMove)) {
6378             if (!appData.premove) {
6379                 DisplayMoveError(_("It is White's turn"));
6380             } else if (toX >= 0 && toY >= 0) {
6381                 premoveToX = toX;
6382                 premoveToY = toY;
6383                 premoveFromX = fromX;
6384                 premoveFromY = fromY;
6385                 premovePromoChar = promoChar;
6386                 gotPremove = 1;
6387                 if (appData.debugMode)
6388                     fprintf(debugFP, "Got premove: fromX %d,"
6389                             "fromY %d, toX %d, toY %d\n",
6390                             fromX, fromY, toX, toY);
6391             }
6392             return;
6393         }
6394         break;
6395
6396       case IcsPlayingWhite:
6397         /* User is moving for White */
6398         if (!WhiteOnMove(currentMove)) {
6399             if (!appData.premove) {
6400                 DisplayMoveError(_("It is Black's turn"));
6401             } else if (toX >= 0 && toY >= 0) {
6402                 premoveToX = toX;
6403                 premoveToY = toY;
6404                 premoveFromX = fromX;
6405                 premoveFromY = fromY;
6406                 premovePromoChar = promoChar;
6407                 gotPremove = 1;
6408                 if (appData.debugMode)
6409                     fprintf(debugFP, "Got premove: fromX %d,"
6410                             "fromY %d, toX %d, toY %d\n",
6411                             fromX, fromY, toX, toY);
6412             }
6413             return;
6414         }
6415         break;
6416
6417       default:
6418         break;
6419
6420       case EditPosition:
6421         /* EditPosition, empty square, or different color piece;
6422            click-click move is possible */
6423         if (toX == -2 || toY == -2) {
6424             boards[0][fromY][fromX] = EmptySquare;
6425             DrawPosition(FALSE, boards[currentMove]);
6426             return;
6427         } else if (toX >= 0 && toY >= 0) {
6428             boards[0][toY][toX] = boards[0][fromY][fromX];
6429             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6430                 if(boards[0][fromY][0] != EmptySquare) {
6431                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6432                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6433                 }
6434             } else
6435             if(fromX == BOARD_RGHT+1) {
6436                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6437                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6438                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6439                 }
6440             } else
6441             boards[0][fromY][fromX] = EmptySquare;
6442             DrawPosition(FALSE, boards[currentMove]);
6443             return;
6444         }
6445         return;
6446     }
6447
6448     if(toX < 0 || toY < 0) return;
6449     pdown = boards[currentMove][fromY][fromX];
6450     pup = boards[currentMove][toY][toX];
6451
6452     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6453     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6454          if( pup != EmptySquare ) return;
6455          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6456            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6457                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6458            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6459            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6460            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6461            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6462          fromY = DROP_RANK;
6463     }
6464
6465     /* [HGM] always test for legality, to get promotion info */
6466     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6467                                          fromY, fromX, toY, toX, promoChar);
6468     /* [HGM] but possibly ignore an IllegalMove result */
6469     if (appData.testLegality) {
6470         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6471             DisplayMoveError(_("Illegal move"));
6472             return;
6473         }
6474     }
6475
6476     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6477 }
6478
6479 /* Common tail of UserMoveEvent and DropMenuEvent */
6480 int
6481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6482      ChessMove moveType;
6483      int fromX, fromY, toX, toY;
6484      /*char*/int promoChar;
6485 {
6486     char *bookHit = 0;
6487
6488     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6489         // [HGM] superchess: suppress promotions to non-available piece
6490         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6491         if(WhiteOnMove(currentMove)) {
6492             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6493         } else {
6494             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6495         }
6496     }
6497
6498     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6499        move type in caller when we know the move is a legal promotion */
6500     if(moveType == NormalMove && promoChar)
6501         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6502
6503     /* [HGM] <popupFix> The following if has been moved here from
6504        UserMoveEvent(). Because it seemed to belong here (why not allow
6505        piece drops in training games?), and because it can only be
6506        performed after it is known to what we promote. */
6507     if (gameMode == Training) {
6508       /* compare the move played on the board to the next move in the
6509        * game. If they match, display the move and the opponent's response.
6510        * If they don't match, display an error message.
6511        */
6512       int saveAnimate;
6513       Board testBoard;
6514       CopyBoard(testBoard, boards[currentMove]);
6515       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6516
6517       if (CompareBoards(testBoard, boards[currentMove+1])) {
6518         ForwardInner(currentMove+1);
6519
6520         /* Autoplay the opponent's response.
6521          * if appData.animate was TRUE when Training mode was entered,
6522          * the response will be animated.
6523          */
6524         saveAnimate = appData.animate;
6525         appData.animate = animateTraining;
6526         ForwardInner(currentMove+1);
6527         appData.animate = saveAnimate;
6528
6529         /* check for the end of the game */
6530         if (currentMove >= forwardMostMove) {
6531           gameMode = PlayFromGameFile;
6532           ModeHighlight();
6533           SetTrainingModeOff();
6534           DisplayInformation(_("End of game"));
6535         }
6536       } else {
6537         DisplayError(_("Incorrect move"), 0);
6538       }
6539       return 1;
6540     }
6541
6542   /* Ok, now we know that the move is good, so we can kill
6543      the previous line in Analysis Mode */
6544   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6545                                 && currentMove < forwardMostMove) {
6546     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6547     else forwardMostMove = currentMove;
6548   }
6549
6550   /* If we need the chess program but it's dead, restart it */
6551   ResurrectChessProgram();
6552
6553   /* A user move restarts a paused game*/
6554   if (pausing)
6555     PauseEvent();
6556
6557   thinkOutput[0] = NULLCHAR;
6558
6559   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6560
6561   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6562     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563     return 1;
6564   }
6565
6566   if (gameMode == BeginningOfGame) {
6567     if (appData.noChessProgram) {
6568       gameMode = EditGame;
6569       SetGameInfo();
6570     } else {
6571       char buf[MSG_SIZ];
6572       gameMode = MachinePlaysBlack;
6573       StartClocks();
6574       SetGameInfo();
6575       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6576       DisplayTitle(buf);
6577       if (first.sendName) {
6578         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6579         SendToProgram(buf, &first);
6580       }
6581       StartClocks();
6582     }
6583     ModeHighlight();
6584   }
6585
6586   /* Relay move to ICS or chess engine */
6587   if (appData.icsActive) {
6588     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6589         gameMode == IcsExamining) {
6590       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6591         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6592         SendToICS("draw ");
6593         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6594       }
6595       // also send plain move, in case ICS does not understand atomic claims
6596       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6597       ics_user_moved = 1;
6598     }
6599   } else {
6600     if (first.sendTime && (gameMode == BeginningOfGame ||
6601                            gameMode == MachinePlaysWhite ||
6602                            gameMode == MachinePlaysBlack)) {
6603       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6604     }
6605     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6606          // [HGM] book: if program might be playing, let it use book
6607         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6608         first.maybeThinking = TRUE;
6609     } else SendMoveToProgram(forwardMostMove-1, &first);
6610     if (currentMove == cmailOldMove + 1) {
6611       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6612     }
6613   }
6614
6615   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6616
6617   switch (gameMode) {
6618   case EditGame:
6619     if(appData.testLegality)
6620     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6621     case MT_NONE:
6622     case MT_CHECK:
6623       break;
6624     case MT_CHECKMATE:
6625     case MT_STAINMATE:
6626       if (WhiteOnMove(currentMove)) {
6627         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6628       } else {
6629         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6630       }
6631       break;
6632     case MT_STALEMATE:
6633       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6634       break;
6635     }
6636     break;
6637
6638   case MachinePlaysBlack:
6639   case MachinePlaysWhite:
6640     /* disable certain menu options while machine is thinking */
6641     SetMachineThinkingEnables();
6642     break;
6643
6644   default:
6645     break;
6646   }
6647
6648   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6649   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6650
6651   if(bookHit) { // [HGM] book: simulate book reply
6652         static char bookMove[MSG_SIZ]; // a bit generous?
6653
6654         programStats.nodes = programStats.depth = programStats.time =
6655         programStats.score = programStats.got_only_move = 0;
6656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6657
6658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6659         strcat(bookMove, bookHit);
6660         HandleMachineMove(bookMove, &first);
6661   }
6662   return 1;
6663 }
6664
6665 void
6666 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6667      Board board;
6668      int flags;
6669      ChessMove kind;
6670      int rf, ff, rt, ft;
6671      VOIDSTAR closure;
6672 {
6673     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6674     Markers *m = (Markers *) closure;
6675     if(rf == fromY && ff == fromX)
6676         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6677                          || kind == WhiteCapturesEnPassant
6678                          || kind == BlackCapturesEnPassant);
6679     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6680 }
6681
6682 void
6683 MarkTargetSquares(int clear)
6684 {
6685   int x, y;
6686   if(!appData.markers || !appData.highlightDragging ||
6687      !appData.testLegality || gameMode == EditPosition) return;
6688   if(clear) {
6689     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6690   } else {
6691     int capt = 0;
6692     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6693     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6694       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6695       if(capt)
6696       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6697     }
6698   }
6699   DrawPosition(TRUE, NULL);
6700 }
6701
6702 int
6703 Explode(Board board, int fromX, int fromY, int toX, int toY)
6704 {
6705     if(gameInfo.variant == VariantAtomic &&
6706        (board[toY][toX] != EmptySquare ||                     // capture?
6707         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6708                          board[fromY][fromX] == BlackPawn   )
6709       )) {
6710         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6711         return TRUE;
6712     }
6713     return FALSE;
6714 }
6715
6716 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6717
6718 int CanPromote(ChessSquare piece, int y)
6719 {
6720         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6721         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6722         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6723            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6724            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6725                                                   gameInfo.variant == VariantMakruk) return FALSE;
6726         return (piece == BlackPawn && y == 1 ||
6727                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6728                 piece == BlackLance && y == 1 ||
6729                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6730 }
6731
6732 void LeftClick(ClickType clickType, int xPix, int yPix)
6733 {
6734     int x, y;
6735     Boolean saveAnimate;
6736     static int second = 0, promotionChoice = 0, clearFlag = 0;
6737     char promoChoice = NULLCHAR;
6738     ChessSquare piece;
6739
6740     if(appData.seekGraph && appData.icsActive && loggedOn &&
6741         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6742         SeekGraphClick(clickType, xPix, yPix, 0);
6743         return;
6744     }
6745
6746     if (clickType == Press) ErrorPopDown();
6747     MarkTargetSquares(1);
6748
6749     x = EventToSquare(xPix, BOARD_WIDTH);
6750     y = EventToSquare(yPix, BOARD_HEIGHT);
6751     if (!flipView && y >= 0) {
6752         y = BOARD_HEIGHT - 1 - y;
6753     }
6754     if (flipView && x >= 0) {
6755         x = BOARD_WIDTH - 1 - x;
6756     }
6757
6758     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6759         defaultPromoChoice = promoSweep;
6760         promoSweep = EmptySquare;   // terminate sweep
6761         promoDefaultAltered = TRUE;
6762         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6763     }
6764
6765     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6766         if(clickType == Release) return; // ignore upclick of click-click destination
6767         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6768         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6769         if(gameInfo.holdingsWidth &&
6770                 (WhiteOnMove(currentMove)
6771                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6772                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6773             // click in right holdings, for determining promotion piece
6774             ChessSquare p = boards[currentMove][y][x];
6775             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6776             if(p != EmptySquare) {
6777                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6778                 fromX = fromY = -1;
6779                 return;
6780             }
6781         }
6782         DrawPosition(FALSE, boards[currentMove]);
6783         return;
6784     }
6785
6786     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6787     if(clickType == Press
6788             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6789               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6790               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6791         return;
6792
6793     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6794         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6795
6796     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6797         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6798                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6799         defaultPromoChoice = DefaultPromoChoice(side);
6800     }
6801
6802     autoQueen = appData.alwaysPromoteToQueen;
6803
6804     if (fromX == -1) {
6805       int originalY = y;
6806       gatingPiece = EmptySquare;
6807       if (clickType != Press) {
6808         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6809             DragPieceEnd(xPix, yPix); dragging = 0;
6810             DrawPosition(FALSE, NULL);
6811         }
6812         return;
6813       }
6814       fromX = x; fromY = y;
6815       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6816          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6817          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6818             /* First square */
6819             if (OKToStartUserMove(fromX, fromY)) {
6820                 second = 0;
6821                 MarkTargetSquares(0);
6822                 DragPieceBegin(xPix, yPix); dragging = 1;
6823                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6824                     promoSweep = defaultPromoChoice;
6825                     selectFlag = 0; lastX = xPix; lastY = yPix;
6826                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6827                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6828                 }
6829                 if (appData.highlightDragging) {
6830                     SetHighlights(fromX, fromY, -1, -1);
6831                 }
6832             } else fromX = fromY = -1;
6833             return;
6834         }
6835     }
6836
6837     /* fromX != -1 */
6838     if (clickType == Press && gameMode != EditPosition) {
6839         ChessSquare fromP;
6840         ChessSquare toP;
6841         int frc;
6842
6843         // ignore off-board to clicks
6844         if(y < 0 || x < 0) return;
6845
6846         /* Check if clicking again on the same color piece */
6847         fromP = boards[currentMove][fromY][fromX];
6848         toP = boards[currentMove][y][x];
6849         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6850         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6851              WhitePawn <= toP && toP <= WhiteKing &&
6852              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6853              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6854             (BlackPawn <= fromP && fromP <= BlackKing &&
6855              BlackPawn <= toP && toP <= BlackKing &&
6856              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6857              !(fromP == BlackKing && toP == BlackRook && frc))) {
6858             /* Clicked again on same color piece -- changed his mind */
6859             second = (x == fromX && y == fromY);
6860             promoDefaultAltered = FALSE;
6861            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6862             if (appData.highlightDragging) {
6863                 SetHighlights(x, y, -1, -1);
6864             } else {
6865                 ClearHighlights();
6866             }
6867             if (OKToStartUserMove(x, y)) {
6868                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6869                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6870                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6871                  gatingPiece = boards[currentMove][fromY][fromX];
6872                 else gatingPiece = EmptySquare;
6873                 fromX = x;
6874                 fromY = y; dragging = 1;
6875                 MarkTargetSquares(0);
6876                 DragPieceBegin(xPix, yPix);
6877                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6878                     promoSweep = defaultPromoChoice;
6879                     selectFlag = 0; lastX = xPix; lastY = yPix;
6880                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6881                 }
6882             }
6883            }
6884            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6885            second = FALSE; 
6886         }
6887         // ignore clicks on holdings
6888         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6889     }
6890
6891     if (clickType == Release && x == fromX && y == fromY) {
6892         DragPieceEnd(xPix, yPix); dragging = 0;
6893         if(clearFlag) {
6894             // a deferred attempt to click-click move an empty square on top of a piece
6895             boards[currentMove][y][x] = EmptySquare;
6896             ClearHighlights();
6897             DrawPosition(FALSE, boards[currentMove]);
6898             fromX = fromY = -1; clearFlag = 0;
6899             return;
6900         }
6901         if (appData.animateDragging) {
6902             /* Undo animation damage if any */
6903             DrawPosition(FALSE, NULL);
6904         }
6905         if (second) {
6906             /* Second up/down in same square; just abort move */
6907             second = 0;
6908             fromX = fromY = -1;
6909             gatingPiece = EmptySquare;
6910             ClearHighlights();
6911             gotPremove = 0;
6912             ClearPremoveHighlights();
6913         } else {
6914             /* First upclick in same square; start click-click mode */
6915             SetHighlights(x, y, -1, -1);
6916         }
6917         return;
6918     }
6919
6920     clearFlag = 0;
6921
6922     /* we now have a different from- and (possibly off-board) to-square */
6923     /* Completed move */
6924     toX = x;
6925     toY = y;
6926     saveAnimate = appData.animate;
6927     if (clickType == Press) {
6928         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6929             // must be Edit Position mode with empty-square selected
6930             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6931             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6932             return;
6933         }
6934         /* Finish clickclick move */
6935         if (appData.animate || appData.highlightLastMove) {
6936             SetHighlights(fromX, fromY, toX, toY);
6937         } else {
6938             ClearHighlights();
6939         }
6940     } else {
6941         /* Finish drag move */
6942         if (appData.highlightLastMove) {
6943             SetHighlights(fromX, fromY, toX, toY);
6944         } else {
6945             ClearHighlights();
6946         }
6947         DragPieceEnd(xPix, yPix); dragging = 0;
6948         /* Don't animate move and drag both */
6949         appData.animate = FALSE;
6950     }
6951
6952     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6953     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6954         ChessSquare piece = boards[currentMove][fromY][fromX];
6955         if(gameMode == EditPosition && piece != EmptySquare &&
6956            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6957             int n;
6958
6959             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6960                 n = PieceToNumber(piece - (int)BlackPawn);
6961                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6962                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6963                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6964             } else
6965             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6966                 n = PieceToNumber(piece);
6967                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6968                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6969                 boards[currentMove][n][BOARD_WIDTH-2]++;
6970             }
6971             boards[currentMove][fromY][fromX] = EmptySquare;
6972         }
6973         ClearHighlights();
6974         fromX = fromY = -1;
6975         DrawPosition(TRUE, boards[currentMove]);
6976         return;
6977     }
6978
6979     // off-board moves should not be highlighted
6980     if(x < 0 || y < 0) ClearHighlights();
6981
6982     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6983
6984     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6985         SetHighlights(fromX, fromY, toX, toY);
6986         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6987             // [HGM] super: promotion to captured piece selected from holdings
6988             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6989             promotionChoice = TRUE;
6990             // kludge follows to temporarily execute move on display, without promoting yet
6991             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6992             boards[currentMove][toY][toX] = p;
6993             DrawPosition(FALSE, boards[currentMove]);
6994             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6995             boards[currentMove][toY][toX] = q;
6996             DisplayMessage("Click in holdings to choose piece", "");
6997             return;
6998         }
6999         PromotionPopUp();
7000     } else {
7001         int oldMove = currentMove;
7002         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7003         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7004         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7005         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7006            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7007             DrawPosition(TRUE, boards[currentMove]);
7008         fromX = fromY = -1;
7009     }
7010     appData.animate = saveAnimate;
7011     if (appData.animate || appData.animateDragging) {
7012         /* Undo animation damage if needed */
7013         DrawPosition(FALSE, NULL);
7014     }
7015 }
7016
7017 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7018 {   // front-end-free part taken out of PieceMenuPopup
7019     int whichMenu; int xSqr, ySqr;
7020
7021     if(seekGraphUp) { // [HGM] seekgraph
7022         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7023         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7024         return -2;
7025     }
7026
7027     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7028          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7029         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7030         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7031         if(action == Press)   {
7032             originalFlip = flipView;
7033             flipView = !flipView; // temporarily flip board to see game from partners perspective
7034             DrawPosition(TRUE, partnerBoard);
7035             DisplayMessage(partnerStatus, "");
7036             partnerUp = TRUE;
7037         } else if(action == Release) {
7038             flipView = originalFlip;
7039             DrawPosition(TRUE, boards[currentMove]);
7040             partnerUp = FALSE;
7041         }
7042         return -2;
7043     }
7044
7045     xSqr = EventToSquare(x, BOARD_WIDTH);
7046     ySqr = EventToSquare(y, BOARD_HEIGHT);
7047     if (action == Release) {
7048         if(pieceSweep != EmptySquare) {
7049             EditPositionMenuEvent(pieceSweep, toX, toY);
7050             pieceSweep = EmptySquare;
7051         } else UnLoadPV(); // [HGM] pv
7052     }
7053     if (action != Press) return -2; // return code to be ignored
7054     switch (gameMode) {
7055       case IcsExamining:
7056         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7057       case EditPosition:
7058         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7059         if (xSqr < 0 || ySqr < 0) return -1;
7060         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7061         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7062         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7063         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7064         NextPiece(0);
7065         return -2;\r
7066       case IcsObserving:
7067         if(!appData.icsEngineAnalyze) return -1;
7068       case IcsPlayingWhite:
7069       case IcsPlayingBlack:
7070         if(!appData.zippyPlay) goto noZip;
7071       case AnalyzeMode:
7072       case AnalyzeFile:
7073       case MachinePlaysWhite:
7074       case MachinePlaysBlack:
7075       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7076         if (!appData.dropMenu) {
7077           LoadPV(x, y);
7078           return 2; // flag front-end to grab mouse events
7079         }
7080         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7081            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7082       case EditGame:
7083       noZip:
7084         if (xSqr < 0 || ySqr < 0) return -1;
7085         if (!appData.dropMenu || appData.testLegality &&
7086             gameInfo.variant != VariantBughouse &&
7087             gameInfo.variant != VariantCrazyhouse) return -1;
7088         whichMenu = 1; // drop menu
7089         break;
7090       default:
7091         return -1;
7092     }
7093
7094     if (((*fromX = xSqr) < 0) ||
7095         ((*fromY = ySqr) < 0)) {
7096         *fromX = *fromY = -1;
7097         return -1;
7098     }
7099     if (flipView)
7100       *fromX = BOARD_WIDTH - 1 - *fromX;
7101     else
7102       *fromY = BOARD_HEIGHT - 1 - *fromY;
7103
7104     return whichMenu;
7105 }
7106
7107 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7108 {
7109 //    char * hint = lastHint;
7110     FrontEndProgramStats stats;
7111
7112     stats.which = cps == &first ? 0 : 1;
7113     stats.depth = cpstats->depth;
7114     stats.nodes = cpstats->nodes;
7115     stats.score = cpstats->score;
7116     stats.time = cpstats->time;
7117     stats.pv = cpstats->movelist;
7118     stats.hint = lastHint;
7119     stats.an_move_index = 0;
7120     stats.an_move_count = 0;
7121
7122     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7123         stats.hint = cpstats->move_name;
7124         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7125         stats.an_move_count = cpstats->nr_moves;
7126     }
7127
7128     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
7129
7130     SetProgramStats( &stats );
7131 }
7132
7133 #define MAXPLAYERS 500
7134
7135 char *
7136 TourneyStandings(int display)
7137 {
7138     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7139     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7140     char result, *p, *names[MAXPLAYERS];
7141
7142     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7143
7144     names[0] = p = strdup(appData.participants);
7145     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7146
7147     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7148
7149     while(result = appData.results[nr]) {
7150         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7151         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7152         wScore = bScore = 0;
7153         switch(result) {
7154           case '+': wScore = 2; break;
7155           case '-': bScore = 2; break;
7156           case '=': wScore = bScore = 1; break;
7157           case ' ':
7158           case '*': return strdup("busy"); // tourney not finished
7159         }
7160         score[w] += wScore;
7161         score[b] += bScore;
7162         games[w]++;
7163         games[b]++;
7164         nr++;
7165     }
7166     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7167     for(w=0; w<nPlayers; w++) {
7168         bScore = -1;
7169         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7170         ranking[w] = b; points[w] = bScore; score[b] = -2;
7171     }
7172     p = malloc(nPlayers*34+1);
7173     for(w=0; w<nPlayers && w<display; w++)
7174         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7175     free(names[0]);
7176     return p;
7177 }
7178
7179 void
7180 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7181 {       // count all piece types
7182         int p, f, r;
7183         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7184         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7185         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7186                 p = board[r][f];
7187                 pCnt[p]++;
7188                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7189                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7190                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7191                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7192                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7193                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7194         }
7195 }
7196
7197 int
7198 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7199 {
7200         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7201         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7202
7203         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7204         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7205         if(myPawns == 2 && nMine == 3) // KPP
7206             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7207         if(myPawns == 1 && nMine == 2) // KP
7208             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7209         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7210             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7211         if(myPawns) return FALSE;
7212         if(pCnt[WhiteRook+side])
7213             return pCnt[BlackRook-side] ||
7214                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7215                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7216                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7217         if(pCnt[WhiteCannon+side]) {
7218             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7219             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7220         }
7221         if(pCnt[WhiteKnight+side])
7222             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7223         return FALSE;
7224 }
7225
7226 int
7227 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7228 {
7229         VariantClass v = gameInfo.variant;
7230
7231         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7232         if(v == VariantShatranj) return TRUE; // always winnable through baring
7233         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7234         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7235
7236         if(v == VariantXiangqi) {
7237                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7238
7239                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7240                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7241                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7242                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7243                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7244                 if(stale) // we have at least one last-rank P plus perhaps C
7245                     return majors // KPKX
7246                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7247                 else // KCA*E*
7248                     return pCnt[WhiteFerz+side] // KCAK
7249                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7250                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7251                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7252
7253         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7254                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7255
7256                 if(nMine == 1) return FALSE; // bare King
7257                 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
7258                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7259                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7260                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7261                 if(pCnt[WhiteKnight+side])
7262                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7263                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7264                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7265                 if(nBishops)
7266                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7267                 if(pCnt[WhiteAlfil+side])
7268                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7269                 if(pCnt[WhiteWazir+side])
7270                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7271         }
7272
7273         return TRUE;
7274 }
7275
7276 int
7277 Adjudicate(ChessProgramState *cps)
7278 {       // [HGM] some adjudications useful with buggy engines
7279         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7280         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7281         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7282         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7283         int k, count = 0; static int bare = 1;
7284         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7285         Boolean canAdjudicate = !appData.icsActive;
7286
7287         // most tests only when we understand the game, i.e. legality-checking on
7288             if( appData.testLegality )
7289             {   /* [HGM] Some more adjudications for obstinate engines */
7290                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7291                 static int moveCount = 6;
7292                 ChessMove result;
7293                 char *reason = NULL;
7294
7295                 /* Count what is on board. */
7296                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7297
7298                 /* Some material-based adjudications that have to be made before stalemate test */
7299                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7300                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7301                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7302                      if(canAdjudicate && appData.checkMates) {
7303                          if(engineOpponent)
7304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7305                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7306                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7307                          return 1;
7308                      }
7309                 }
7310
7311                 /* Bare King in Shatranj (loses) or Losers (wins) */
7312                 if( nrW == 1 || nrB == 1) {
7313                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7314                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7315                      if(canAdjudicate && appData.checkMates) {
7316                          if(engineOpponent)
7317                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7318                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7319                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7320                          return 1;
7321                      }
7322                   } else
7323                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7324                   {    /* bare King */
7325                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7326                         if(canAdjudicate && appData.checkMates) {
7327                             /* but only adjudicate if adjudication enabled */
7328                             if(engineOpponent)
7329                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7330                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7331                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7332                             return 1;
7333                         }
7334                   }
7335                 } else bare = 1;
7336
7337
7338             // don't wait for engine to announce game end if we can judge ourselves
7339             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7340               case MT_CHECK:
7341                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7342                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7343                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7344                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7345                             checkCnt++;
7346                         if(checkCnt >= 2) {
7347                             reason = "Xboard adjudication: 3rd check";
7348                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7349                             break;
7350                         }
7351                     }
7352                 }
7353               case MT_NONE:
7354               default:
7355                 break;
7356               case MT_STALEMATE:
7357               case MT_STAINMATE:
7358                 reason = "Xboard adjudication: Stalemate";
7359                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7360                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7361                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7362                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7363                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7364                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7365                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7366                                                                         EP_CHECKMATE : EP_WINS);
7367                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7368                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7369                 }
7370                 break;
7371               case MT_CHECKMATE:
7372                 reason = "Xboard adjudication: Checkmate";
7373                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7374                 break;
7375             }
7376
7377                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7378                     case EP_STALEMATE:
7379                         result = GameIsDrawn; break;
7380                     case EP_CHECKMATE:
7381                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7382                     case EP_WINS:
7383                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7384                     default:
7385                         result = EndOfFile;
7386                 }
7387                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7388                     if(engineOpponent)
7389                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7390                     GameEnds( result, reason, GE_XBOARD );
7391                     return 1;
7392                 }
7393
7394                 /* Next absolutely insufficient mating material. */
7395                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7396                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7397                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7398
7399                      /* always flag draws, for judging claims */
7400                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7401
7402                      if(canAdjudicate && appData.materialDraws) {
7403                          /* but only adjudicate them if adjudication enabled */
7404                          if(engineOpponent) {
7405                            SendToProgram("force\n", engineOpponent); // suppress reply
7406                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7407                          }
7408                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7409                          return 1;
7410                      }
7411                 }
7412
7413                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7414                 if(gameInfo.variant == VariantXiangqi ?
7415                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7416                  : nrW + nrB == 4 &&
7417                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7418                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7419                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7420                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7421                    ) ) {
7422                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7423                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7424                           if(engineOpponent) {
7425                             SendToProgram("force\n", engineOpponent); // suppress reply
7426                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7427                           }
7428                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7429                           return 1;
7430                      }
7431                 } else moveCount = 6;
7432             }
7433         if (appData.debugMode) { int i;
7434             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7435                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7436                     appData.drawRepeats);
7437             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7438               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7439
7440         }
7441
7442         // Repetition draws and 50-move rule can be applied independently of legality testing
7443
7444                 /* Check for rep-draws */
7445                 count = 0;
7446                 for(k = forwardMostMove-2;
7447                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7448                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7449                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7450                     k-=2)
7451                 {   int rights=0;
7452                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7453                         /* compare castling rights */
7454                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7455                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7456                                 rights++; /* King lost rights, while rook still had them */
7457                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7458                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7459                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7460                                    rights++; /* but at least one rook lost them */
7461                         }
7462                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7463                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7464                                 rights++;
7465                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7466                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7467                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7468                                    rights++;
7469                         }
7470                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7471                             && appData.drawRepeats > 1) {
7472                              /* adjudicate after user-specified nr of repeats */
7473                              int result = GameIsDrawn;
7474                              char *details = "XBoard adjudication: repetition draw";
7475                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7476                                 // [HGM] xiangqi: check for forbidden perpetuals
7477                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7478                                 for(m=forwardMostMove; m>k; m-=2) {
7479                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7480                                         ourPerpetual = 0; // the current mover did not always check
7481                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7482                                         hisPerpetual = 0; // the opponent did not always check
7483                                 }
7484                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7485                                                                         ourPerpetual, hisPerpetual);
7486                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7487                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7488                                     details = "Xboard adjudication: perpetual checking";
7489                                 } else
7490                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7491                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7492                                 } else
7493                                 // Now check for perpetual chases
7494                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7495                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7496                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7497                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7498                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7499                                         details = "Xboard adjudication: perpetual chasing";
7500                                     } else
7501                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7502                                         break; // Abort repetition-checking loop.
7503                                 }
7504                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7505                              }
7506                              if(engineOpponent) {
7507                                SendToProgram("force\n", engineOpponent); // suppress reply
7508                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7509                              }
7510                              GameEnds( result, details, GE_XBOARD );
7511                              return 1;
7512                         }
7513                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7514                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7515                     }
7516                 }
7517
7518                 /* Now we test for 50-move draws. Determine ply count */
7519                 count = forwardMostMove;
7520                 /* look for last irreversble move */
7521                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7522                     count--;
7523                 /* if we hit starting position, add initial plies */
7524                 if( count == backwardMostMove )
7525                     count -= initialRulePlies;
7526                 count = forwardMostMove - count;
7527                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7528                         // adjust reversible move counter for checks in Xiangqi
7529                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7530                         if(i < backwardMostMove) i = backwardMostMove;
7531                         while(i <= forwardMostMove) {
7532                                 lastCheck = inCheck; // check evasion does not count
7533                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7534                                 if(inCheck || lastCheck) count--; // check does not count
7535                                 i++;
7536                         }
7537                 }
7538                 if( count >= 100)
7539                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7540                          /* this is used to judge if draw claims are legal */
7541                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7547                          return 1;
7548                 }
7549
7550                 /* if draw offer is pending, treat it as a draw claim
7551                  * when draw condition present, to allow engines a way to
7552                  * claim draws before making their move to avoid a race
7553                  * condition occurring after their move
7554                  */
7555                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7556                          char *p = NULL;
7557                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7558                              p = "Draw claim: 50-move rule";
7559                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7560                              p = "Draw claim: 3-fold repetition";
7561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7562                              p = "Draw claim: insufficient mating material";
7563                          if( p != NULL && canAdjudicate) {
7564                              if(engineOpponent) {
7565                                SendToProgram("force\n", engineOpponent); // suppress reply
7566                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7567                              }
7568                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7569                              return 1;
7570                          }
7571                 }
7572
7573                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7574                     if(engineOpponent) {
7575                       SendToProgram("force\n", engineOpponent); // suppress reply
7576                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7577                     }
7578                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7579                     return 1;
7580                 }
7581         return 0;
7582 }
7583
7584 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7585 {   // [HGM] book: this routine intercepts moves to simulate book replies
7586     char *bookHit = NULL;
7587
7588     //first determine if the incoming move brings opponent into his book
7589     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7590         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7591     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7592     if(bookHit != NULL && !cps->bookSuspend) {
7593         // make sure opponent is not going to reply after receiving move to book position
7594         SendToProgram("force\n", cps);
7595         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7596     }
7597     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7598     // now arrange restart after book miss
7599     if(bookHit) {
7600         // after a book hit we never send 'go', and the code after the call to this routine
7601         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7602         char buf[MSG_SIZ], *move = bookHit;
7603         if(cps->useSAN) {
7604             int fromX, fromY, toX, toY;
7605             char promoChar;
7606             ChessMove moveType;
7607             move = buf + 30;
7608             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7609                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7610                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7611                                     PosFlags(forwardMostMove),
7612                                     fromY, fromX, toY, toX, promoChar, move);
7613             } else {
7614                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7615                 bookHit = NULL;
7616             }
7617         }
7618         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7619         SendToProgram(buf, cps);
7620         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7621     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7622         SendToProgram("go\n", cps);
7623         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7624     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7625         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7626             SendToProgram("go\n", cps);
7627         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7628     }
7629     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7630 }
7631
7632 char *savedMessage;
7633 ChessProgramState *savedState;
7634 void DeferredBookMove(void)
7635 {
7636         if(savedState->lastPing != savedState->lastPong)
7637                     ScheduleDelayedEvent(DeferredBookMove, 10);
7638         else
7639         HandleMachineMove(savedMessage, savedState);
7640 }
7641
7642 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7643
7644 void
7645 HandleMachineMove(message, cps)
7646      char *message;
7647      ChessProgramState *cps;
7648 {
7649     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7650     char realname[MSG_SIZ];
7651     int fromX, fromY, toX, toY;
7652     ChessMove moveType;
7653     char promoChar;
7654     char *p;
7655     int machineWhite;
7656     char *bookHit;
7657
7658     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7659         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7660         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7661         pairingReceived = 1;
7662         NextMatchGame();
7663         return; // Skim the pairing messages here.
7664     }
7665
7666     cps->userError = 0;
7667
7668 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7669     /*
7670      * Kludge to ignore BEL characters
7671      */
7672     while (*message == '\007') message++;
7673
7674     /*
7675      * [HGM] engine debug message: ignore lines starting with '#' character
7676      */
7677     if(cps->debug && *message == '#') return;
7678
7679     /*
7680      * Look for book output
7681      */
7682     if (cps == &first && bookRequested) {
7683         if (message[0] == '\t' || message[0] == ' ') {
7684             /* Part of the book output is here; append it */
7685             strcat(bookOutput, message);
7686             strcat(bookOutput, "  \n");
7687             return;
7688         } else if (bookOutput[0] != NULLCHAR) {
7689             /* All of book output has arrived; display it */
7690             char *p = bookOutput;
7691             while (*p != NULLCHAR) {
7692                 if (*p == '\t') *p = ' ';
7693                 p++;
7694             }
7695             DisplayInformation(bookOutput);
7696             bookRequested = FALSE;
7697             /* Fall through to parse the current output */
7698         }
7699     }
7700
7701     /*
7702      * Look for machine move.
7703      */
7704     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7705         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7706     {
7707         /* This method is only useful on engines that support ping */
7708         if (cps->lastPing != cps->lastPong) {
7709           if (gameMode == BeginningOfGame) {
7710             /* Extra move from before last new; ignore */
7711             if (appData.debugMode) {
7712                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7713             }
7714           } else {
7715             if (appData.debugMode) {
7716                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7717                         cps->which, gameMode);
7718             }
7719
7720             SendToProgram("undo\n", cps);
7721           }
7722           return;
7723         }
7724
7725         switch (gameMode) {
7726           case BeginningOfGame:
7727             /* Extra move from before last reset; ignore */
7728             if (appData.debugMode) {
7729                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7730             }
7731             return;
7732
7733           case EndOfGame:
7734           case IcsIdle:
7735           default:
7736             /* Extra move after we tried to stop.  The mode test is
7737                not a reliable way of detecting this problem, but it's
7738                the best we can do on engines that don't support ping.
7739             */
7740             if (appData.debugMode) {
7741                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7742                         cps->which, gameMode);
7743             }
7744             SendToProgram("undo\n", cps);
7745             return;
7746
7747           case MachinePlaysWhite:
7748           case IcsPlayingWhite:
7749             machineWhite = TRUE;
7750             break;
7751
7752           case MachinePlaysBlack:
7753           case IcsPlayingBlack:
7754             machineWhite = FALSE;
7755             break;
7756
7757           case TwoMachinesPlay:
7758             machineWhite = (cps->twoMachinesColor[0] == 'w');
7759             break;
7760         }
7761         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7762             if (appData.debugMode) {
7763                 fprintf(debugFP,
7764                         "Ignoring move out of turn by %s, gameMode %d"
7765                         ", forwardMost %d\n",
7766                         cps->which, gameMode, forwardMostMove);
7767             }
7768             return;
7769         }
7770
7771     if (appData.debugMode) { int f = forwardMostMove;
7772         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7773                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7774                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7775     }
7776         if(cps->alphaRank) AlphaRank(machineMove, 4);
7777         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7778                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7779             /* Machine move could not be parsed; ignore it. */
7780           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7781                     machineMove, _(cps->which));
7782             DisplayError(buf1, 0);
7783             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7784                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7785             if (gameMode == TwoMachinesPlay) {
7786               GameEnds(machineWhite ? BlackWins : WhiteWins,
7787                        buf1, GE_XBOARD);
7788             }
7789             return;
7790         }
7791
7792         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7793         /* So we have to redo legality test with true e.p. status here,  */
7794         /* to make sure an illegal e.p. capture does not slip through,   */
7795         /* to cause a forfeit on a justified illegal-move complaint      */
7796         /* of the opponent.                                              */
7797         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7798            ChessMove moveType;
7799            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7800                              fromY, fromX, toY, toX, promoChar);
7801             if (appData.debugMode) {
7802                 int i;
7803                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7804                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7805                 fprintf(debugFP, "castling rights\n");
7806             }
7807             if(moveType == IllegalMove) {
7808               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7809                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7810                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7811                            buf1, GE_XBOARD);
7812                 return;
7813            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7814            /* [HGM] Kludge to handle engines that send FRC-style castling
7815               when they shouldn't (like TSCP-Gothic) */
7816            switch(moveType) {
7817              case WhiteASideCastleFR:
7818              case BlackASideCastleFR:
7819                toX+=2;
7820                currentMoveString[2]++;
7821                break;
7822              case WhiteHSideCastleFR:
7823              case BlackHSideCastleFR:
7824                toX--;
7825                currentMoveString[2]--;
7826                break;
7827              default: ; // nothing to do, but suppresses warning of pedantic compilers
7828            }
7829         }
7830         hintRequested = FALSE;
7831         lastHint[0] = NULLCHAR;
7832         bookRequested = FALSE;
7833         /* Program may be pondering now */
7834         cps->maybeThinking = TRUE;
7835         if (cps->sendTime == 2) cps->sendTime = 1;
7836         if (cps->offeredDraw) cps->offeredDraw--;
7837
7838         /* [AS] Save move info*/
7839         pvInfoList[ forwardMostMove ].score = programStats.score;
7840         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7841         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7842
7843         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7844
7845         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7846         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7847             int count = 0;
7848
7849             while( count < adjudicateLossPlies ) {
7850                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7851
7852                 if( count & 1 ) {
7853                     score = -score; /* Flip score for winning side */
7854                 }
7855
7856                 if( score > adjudicateLossThreshold ) {
7857                     break;
7858                 }
7859
7860                 count++;
7861             }
7862
7863             if( count >= adjudicateLossPlies ) {
7864                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7865
7866                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7867                     "Xboard adjudication",
7868                     GE_XBOARD );
7869
7870                 return;
7871             }
7872         }
7873
7874         if(Adjudicate(cps)) {
7875             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7876             return; // [HGM] adjudicate: for all automatic game ends
7877         }
7878
7879 #if ZIPPY
7880         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7881             first.initDone) {
7882           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7883                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7884                 SendToICS("draw ");
7885                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7886           }
7887           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7888           ics_user_moved = 1;
7889           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7890                 char buf[3*MSG_SIZ];
7891
7892                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7893                         programStats.score / 100.,
7894                         programStats.depth,
7895                         programStats.time / 100.,
7896                         (unsigned int)programStats.nodes,
7897                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7898                         programStats.movelist);
7899                 SendToICS(buf);
7900 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7901           }
7902         }
7903 #endif
7904
7905         /* [AS] Clear stats for next move */
7906         ClearProgramStats();
7907         thinkOutput[0] = NULLCHAR;
7908         hiddenThinkOutputState = 0;
7909
7910         bookHit = NULL;
7911         if (gameMode == TwoMachinesPlay) {
7912             /* [HGM] relaying draw offers moved to after reception of move */
7913             /* and interpreting offer as claim if it brings draw condition */
7914             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7915                 SendToProgram("draw\n", cps->other);
7916             }
7917             if (cps->other->sendTime) {
7918                 SendTimeRemaining(cps->other,
7919                                   cps->other->twoMachinesColor[0] == 'w');
7920             }
7921             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7922             if (firstMove && !bookHit) {
7923                 firstMove = FALSE;
7924                 if (cps->other->useColors) {
7925                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7926                 }
7927                 SendToProgram("go\n", cps->other);
7928             }
7929             cps->other->maybeThinking = TRUE;
7930         }
7931
7932         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7933
7934         if (!pausing && appData.ringBellAfterMoves) {
7935             RingBell();
7936         }
7937
7938         /*
7939          * Reenable menu items that were disabled while
7940          * machine was thinking
7941          */
7942         if (gameMode != TwoMachinesPlay)
7943             SetUserThinkingEnables();
7944
7945         // [HGM] book: after book hit opponent has received move and is now in force mode
7946         // force the book reply into it, and then fake that it outputted this move by jumping
7947         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7948         if(bookHit) {
7949                 static char bookMove[MSG_SIZ]; // a bit generous?
7950
7951                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7952                 strcat(bookMove, bookHit);
7953                 message = bookMove;
7954                 cps = cps->other;
7955                 programStats.nodes = programStats.depth = programStats.time =
7956                 programStats.score = programStats.got_only_move = 0;
7957                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7958
7959                 if(cps->lastPing != cps->lastPong) {
7960                     savedMessage = message; // args for deferred call
7961                     savedState = cps;
7962                     ScheduleDelayedEvent(DeferredBookMove, 10);
7963                     return;
7964                 }
7965                 goto FakeBookMove;
7966         }
7967
7968         return;
7969     }
7970
7971     /* Set special modes for chess engines.  Later something general
7972      *  could be added here; for now there is just one kludge feature,
7973      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7974      *  when "xboard" is given as an interactive command.
7975      */
7976     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7977         cps->useSigint = FALSE;
7978         cps->useSigterm = FALSE;
7979     }
7980     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7981       ParseFeatures(message+8, cps);
7982       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7983     }
7984
7985     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7986       int dummy, s=6; char buf[MSG_SIZ];
7987       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7988       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7989       ParseFEN(boards[0], &dummy, message+s);
7990       DrawPosition(TRUE, boards[0]);
7991       startedFromSetupPosition = TRUE;
7992       return;
7993     }
7994     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7995      * want this, I was asked to put it in, and obliged.
7996      */
7997     if (!strncmp(message, "setboard ", 9)) {
7998         Board initial_position;
7999
8000         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8001
8002         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8003             DisplayError(_("Bad FEN received from engine"), 0);
8004             return ;
8005         } else {
8006            Reset(TRUE, FALSE);
8007            CopyBoard(boards[0], initial_position);
8008            initialRulePlies = FENrulePlies;
8009            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8010            else gameMode = MachinePlaysBlack;
8011            DrawPosition(FALSE, boards[currentMove]);
8012         }
8013         return;
8014     }
8015
8016     /*
8017      * Look for communication commands
8018      */
8019     if (!strncmp(message, "telluser ", 9)) {
8020         if(message[9] == '\\' && message[10] == '\\')
8021             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8022         DisplayNote(message + 9);
8023         return;
8024     }
8025     if (!strncmp(message, "tellusererror ", 14)) {
8026         cps->userError = 1;
8027         if(message[14] == '\\' && message[15] == '\\')
8028             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8029         DisplayError(message + 14, 0);
8030         return;
8031     }
8032     if (!strncmp(message, "tellopponent ", 13)) {
8033       if (appData.icsActive) {
8034         if (loggedOn) {
8035           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8036           SendToICS(buf1);
8037         }
8038       } else {
8039         DisplayNote(message + 13);
8040       }
8041       return;
8042     }
8043     if (!strncmp(message, "tellothers ", 11)) {
8044       if (appData.icsActive) {
8045         if (loggedOn) {
8046           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8047           SendToICS(buf1);
8048         }
8049       }
8050       return;
8051     }
8052     if (!strncmp(message, "tellall ", 8)) {
8053       if (appData.icsActive) {
8054         if (loggedOn) {
8055           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8056           SendToICS(buf1);
8057         }
8058       } else {
8059         DisplayNote(message + 8);
8060       }
8061       return;
8062     }
8063     if (strncmp(message, "warning", 7) == 0) {
8064         /* Undocumented feature, use tellusererror in new code */
8065         DisplayError(message, 0);
8066         return;
8067     }
8068     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8069         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8070         strcat(realname, " query");
8071         AskQuestion(realname, buf2, buf1, cps->pr);
8072         return;
8073     }
8074     /* Commands from the engine directly to ICS.  We don't allow these to be
8075      *  sent until we are logged on. Crafty kibitzes have been known to
8076      *  interfere with the login process.
8077      */
8078     if (loggedOn) {
8079         if (!strncmp(message, "tellics ", 8)) {
8080             SendToICS(message + 8);
8081             SendToICS("\n");
8082             return;
8083         }
8084         if (!strncmp(message, "tellicsnoalias ", 15)) {
8085             SendToICS(ics_prefix);
8086             SendToICS(message + 15);
8087             SendToICS("\n");
8088             return;
8089         }
8090         /* The following are for backward compatibility only */
8091         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8092             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8093             SendToICS(ics_prefix);
8094             SendToICS(message);
8095             SendToICS("\n");
8096             return;
8097         }
8098     }
8099     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8100         return;
8101     }
8102     /*
8103      * If the move is illegal, cancel it and redraw the board.
8104      * Also deal with other error cases.  Matching is rather loose
8105      * here to accommodate engines written before the spec.
8106      */
8107     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8108         strncmp(message, "Error", 5) == 0) {
8109         if (StrStr(message, "name") ||
8110             StrStr(message, "rating") || StrStr(message, "?") ||
8111             StrStr(message, "result") || StrStr(message, "board") ||
8112             StrStr(message, "bk") || StrStr(message, "computer") ||
8113             StrStr(message, "variant") || StrStr(message, "hint") ||
8114             StrStr(message, "random") || StrStr(message, "depth") ||
8115             StrStr(message, "accepted")) {
8116             return;
8117         }
8118         if (StrStr(message, "protover")) {
8119           /* Program is responding to input, so it's apparently done
8120              initializing, and this error message indicates it is
8121              protocol version 1.  So we don't need to wait any longer
8122              for it to initialize and send feature commands. */
8123           FeatureDone(cps, 1);
8124           cps->protocolVersion = 1;
8125           return;
8126         }
8127         cps->maybeThinking = FALSE;
8128
8129         if (StrStr(message, "draw")) {
8130             /* Program doesn't have "draw" command */
8131             cps->sendDrawOffers = 0;
8132             return;
8133         }
8134         if (cps->sendTime != 1 &&
8135             (StrStr(message, "time") || StrStr(message, "otim"))) {
8136           /* Program apparently doesn't have "time" or "otim" command */
8137           cps->sendTime = 0;
8138           return;
8139         }
8140         if (StrStr(message, "analyze")) {
8141             cps->analysisSupport = FALSE;
8142             cps->analyzing = FALSE;
8143             Reset(FALSE, TRUE);
8144             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8145             DisplayError(buf2, 0);
8146             return;
8147         }
8148         if (StrStr(message, "(no matching move)st")) {
8149           /* Special kludge for GNU Chess 4 only */
8150           cps->stKludge = TRUE;
8151           SendTimeControl(cps, movesPerSession, timeControl,
8152                           timeIncrement, appData.searchDepth,
8153                           searchTime);
8154           return;
8155         }
8156         if (StrStr(message, "(no matching move)sd")) {
8157           /* Special kludge for GNU Chess 4 only */
8158           cps->sdKludge = TRUE;
8159           SendTimeControl(cps, movesPerSession, timeControl,
8160                           timeIncrement, appData.searchDepth,
8161                           searchTime);
8162           return;
8163         }
8164         if (!StrStr(message, "llegal")) {
8165             return;
8166         }
8167         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8168             gameMode == IcsIdle) return;
8169         if (forwardMostMove <= backwardMostMove) return;
8170         if (pausing) PauseEvent();
8171       if(appData.forceIllegal) {
8172             // [HGM] illegal: machine refused move; force position after move into it
8173           SendToProgram("force\n", cps);
8174           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8175                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8176                 // when black is to move, while there might be nothing on a2 or black
8177                 // might already have the move. So send the board as if white has the move.
8178                 // But first we must change the stm of the engine, as it refused the last move
8179                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8180                 if(WhiteOnMove(forwardMostMove)) {
8181                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8182                     SendBoard(cps, forwardMostMove); // kludgeless board
8183                 } else {
8184                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8185                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8186                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8187                 }
8188           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8189             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8190                  gameMode == TwoMachinesPlay)
8191               SendToProgram("go\n", cps);
8192             return;
8193       } else
8194         if (gameMode == PlayFromGameFile) {
8195             /* Stop reading this game file */
8196             gameMode = EditGame;
8197             ModeHighlight();
8198         }
8199         /* [HGM] illegal-move claim should forfeit game when Xboard */
8200         /* only passes fully legal moves                            */
8201         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8202             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8203                                 "False illegal-move claim", GE_XBOARD );
8204             return; // do not take back move we tested as valid
8205         }
8206         currentMove = forwardMostMove-1;
8207         DisplayMove(currentMove-1); /* before DisplayMoveError */
8208         SwitchClocks(forwardMostMove-1); // [HGM] race
8209         DisplayBothClocks();
8210         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8211                 parseList[currentMove], _(cps->which));
8212         DisplayMoveError(buf1);
8213         DrawPosition(FALSE, boards[currentMove]);
8214         return;
8215     }
8216     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8217         /* Program has a broken "time" command that
8218            outputs a string not ending in newline.
8219            Don't use it. */
8220         cps->sendTime = 0;
8221     }
8222
8223     /*
8224      * If chess program startup fails, exit with an error message.
8225      * Attempts to recover here are futile.
8226      */
8227     if ((StrStr(message, "unknown host") != NULL)
8228         || (StrStr(message, "No remote directory") != NULL)
8229         || (StrStr(message, "not found") != NULL)
8230         || (StrStr(message, "No such file") != NULL)
8231         || (StrStr(message, "can't alloc") != NULL)
8232         || (StrStr(message, "Permission denied") != NULL)) {
8233
8234         cps->maybeThinking = FALSE;
8235         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8236                 _(cps->which), cps->program, cps->host, message);
8237         RemoveInputSource(cps->isr);
8238         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8239             if(cps == &first) appData.noChessProgram = TRUE;
8240             DisplayError(buf1, 0);
8241         }
8242         return;
8243     }
8244
8245     /*
8246      * Look for hint output
8247      */
8248     if (sscanf(message, "Hint: %s", buf1) == 1) {
8249         if (cps == &first && hintRequested) {
8250             hintRequested = FALSE;
8251             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8252                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8253                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8254                                     PosFlags(forwardMostMove),
8255                                     fromY, fromX, toY, toX, promoChar, buf1);
8256                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8257                 DisplayInformation(buf2);
8258             } else {
8259                 /* Hint move could not be parsed!? */
8260               snprintf(buf2, sizeof(buf2),
8261                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8262                         buf1, _(cps->which));
8263                 DisplayError(buf2, 0);
8264             }
8265         } else {
8266           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8267         }
8268         return;
8269     }
8270
8271     /*
8272      * Ignore other messages if game is not in progress
8273      */
8274     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8275         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8276
8277     /*
8278      * look for win, lose, draw, or draw offer
8279      */
8280     if (strncmp(message, "1-0", 3) == 0) {
8281         char *p, *q, *r = "";
8282         p = strchr(message, '{');
8283         if (p) {
8284             q = strchr(p, '}');
8285             if (q) {
8286                 *q = NULLCHAR;
8287                 r = p + 1;
8288             }
8289         }
8290         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8291         return;
8292     } else if (strncmp(message, "0-1", 3) == 0) {
8293         char *p, *q, *r = "";
8294         p = strchr(message, '{');
8295         if (p) {
8296             q = strchr(p, '}');
8297             if (q) {
8298                 *q = NULLCHAR;
8299                 r = p + 1;
8300             }
8301         }
8302         /* Kludge for Arasan 4.1 bug */
8303         if (strcmp(r, "Black resigns") == 0) {
8304             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8305             return;
8306         }
8307         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8308         return;
8309     } else if (strncmp(message, "1/2", 3) == 0) {
8310         char *p, *q, *r = "";
8311         p = strchr(message, '{');
8312         if (p) {
8313             q = strchr(p, '}');
8314             if (q) {
8315                 *q = NULLCHAR;
8316                 r = p + 1;
8317             }
8318         }
8319
8320         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8321         return;
8322
8323     } else if (strncmp(message, "White resign", 12) == 0) {
8324         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8325         return;
8326     } else if (strncmp(message, "Black resign", 12) == 0) {
8327         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8328         return;
8329     } else if (strncmp(message, "White matches", 13) == 0 ||
8330                strncmp(message, "Black matches", 13) == 0   ) {
8331         /* [HGM] ignore GNUShogi noises */
8332         return;
8333     } else if (strncmp(message, "White", 5) == 0 &&
8334                message[5] != '(' &&
8335                StrStr(message, "Black") == NULL) {
8336         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8337         return;
8338     } else if (strncmp(message, "Black", 5) == 0 &&
8339                message[5] != '(') {
8340         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8341         return;
8342     } else if (strcmp(message, "resign") == 0 ||
8343                strcmp(message, "computer resigns") == 0) {
8344         switch (gameMode) {
8345           case MachinePlaysBlack:
8346           case IcsPlayingBlack:
8347             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8348             break;
8349           case MachinePlaysWhite:
8350           case IcsPlayingWhite:
8351             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8352             break;
8353           case TwoMachinesPlay:
8354             if (cps->twoMachinesColor[0] == 'w')
8355               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8356             else
8357               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8358             break;
8359           default:
8360             /* can't happen */
8361             break;
8362         }
8363         return;
8364     } else if (strncmp(message, "opponent mates", 14) == 0) {
8365         switch (gameMode) {
8366           case MachinePlaysBlack:
8367           case IcsPlayingBlack:
8368             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8369             break;
8370           case MachinePlaysWhite:
8371           case IcsPlayingWhite:
8372             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8373             break;
8374           case TwoMachinesPlay:
8375             if (cps->twoMachinesColor[0] == 'w')
8376               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8377             else
8378               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8379             break;
8380           default:
8381             /* can't happen */
8382             break;
8383         }
8384         return;
8385     } else if (strncmp(message, "computer mates", 14) == 0) {
8386         switch (gameMode) {
8387           case MachinePlaysBlack:
8388           case IcsPlayingBlack:
8389             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8390             break;
8391           case MachinePlaysWhite:
8392           case IcsPlayingWhite:
8393             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8394             break;
8395           case TwoMachinesPlay:
8396             if (cps->twoMachinesColor[0] == 'w')
8397               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8398             else
8399               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8400             break;
8401           default:
8402             /* can't happen */
8403             break;
8404         }
8405         return;
8406     } else if (strncmp(message, "checkmate", 9) == 0) {
8407         if (WhiteOnMove(forwardMostMove)) {
8408             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8409         } else {
8410             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8411         }
8412         return;
8413     } else if (strstr(message, "Draw") != NULL ||
8414                strstr(message, "game is a draw") != NULL) {
8415         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8416         return;
8417     } else if (strstr(message, "offer") != NULL &&
8418                strstr(message, "draw") != NULL) {
8419 #if ZIPPY
8420         if (appData.zippyPlay && first.initDone) {
8421             /* Relay offer to ICS */
8422             SendToICS(ics_prefix);
8423             SendToICS("draw\n");
8424         }
8425 #endif
8426         cps->offeredDraw = 2; /* valid until this engine moves twice */
8427         if (gameMode == TwoMachinesPlay) {
8428             if (cps->other->offeredDraw) {
8429                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8430             /* [HGM] in two-machine mode we delay relaying draw offer      */
8431             /* until after we also have move, to see if it is really claim */
8432             }
8433         } else if (gameMode == MachinePlaysWhite ||
8434                    gameMode == MachinePlaysBlack) {
8435           if (userOfferedDraw) {
8436             DisplayInformation(_("Machine accepts your draw offer"));
8437             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8438           } else {
8439             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8440           }
8441         }
8442     }
8443
8444
8445     /*
8446      * Look for thinking output
8447      */
8448     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8449           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8450                                 ) {
8451         int plylev, mvleft, mvtot, curscore, time;
8452         char mvname[MOVE_LEN];
8453         u64 nodes; // [DM]
8454         char plyext;
8455         int ignore = FALSE;
8456         int prefixHint = FALSE;
8457         mvname[0] = NULLCHAR;
8458
8459         switch (gameMode) {
8460           case MachinePlaysBlack:
8461           case IcsPlayingBlack:
8462             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8463             break;
8464           case MachinePlaysWhite:
8465           case IcsPlayingWhite:
8466             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8467             break;
8468           case AnalyzeMode:
8469           case AnalyzeFile:
8470             break;
8471           case IcsObserving: /* [DM] icsEngineAnalyze */
8472             if (!appData.icsEngineAnalyze) ignore = TRUE;
8473             break;
8474           case TwoMachinesPlay:
8475             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8476                 ignore = TRUE;
8477             }
8478             break;
8479           default:
8480             ignore = TRUE;
8481             break;
8482         }
8483
8484         if (!ignore) {
8485             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8486             buf1[0] = NULLCHAR;
8487             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8488                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8489
8490                 if (plyext != ' ' && plyext != '\t') {
8491                     time *= 100;
8492                 }
8493
8494                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8495                 if( cps->scoreIsAbsolute &&
8496                     ( gameMode == MachinePlaysBlack ||
8497                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8498                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8499                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8500                      !WhiteOnMove(currentMove)
8501                     ) )
8502                 {
8503                     curscore = -curscore;
8504                 }
8505
8506
8507                 tempStats.depth = plylev;
8508                 tempStats.nodes = nodes;
8509                 tempStats.time = time;
8510                 tempStats.score = curscore;
8511                 tempStats.got_only_move = 0;
8512
8513                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8514                         int ticklen;
8515
8516                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8517                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8518                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8519                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8520                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8521                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8522                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8523                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8524                 }
8525
8526                 /* Buffer overflow protection */
8527                 if (buf1[0] != NULLCHAR) {
8528                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8529                         && appData.debugMode) {
8530                         fprintf(debugFP,
8531                                 "PV is too long; using the first %u bytes.\n",
8532                                 (unsigned) sizeof(tempStats.movelist) - 1);
8533                     }
8534
8535                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8536                 } else {
8537                     sprintf(tempStats.movelist, " no PV\n");
8538                 }
8539
8540                 if (tempStats.seen_stat) {
8541                     tempStats.ok_to_send = 1;
8542                 }
8543
8544                 if (strchr(tempStats.movelist, '(') != NULL) {
8545                     tempStats.line_is_book = 1;
8546                     tempStats.nr_moves = 0;
8547                     tempStats.moves_left = 0;
8548                 } else {
8549                     tempStats.line_is_book = 0;
8550                 }
8551
8552                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8553                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8554
8555                 SendProgramStatsToFrontend( cps, &tempStats );
8556
8557                 /*
8558                     [AS] Protect the thinkOutput buffer from overflow... this
8559                     is only useful if buf1 hasn't overflowed first!
8560                 */
8561                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8562                          plylev,
8563                          (gameMode == TwoMachinesPlay ?
8564                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8565                          ((double) curscore) / 100.0,
8566                          prefixHint ? lastHint : "",
8567                          prefixHint ? " " : "" );
8568
8569                 if( buf1[0] != NULLCHAR ) {
8570                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8571
8572                     if( strlen(buf1) > max_len ) {
8573                         if( appData.debugMode) {
8574                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8575                         }
8576                         buf1[max_len+1] = '\0';
8577                     }
8578
8579                     strcat( thinkOutput, buf1 );
8580                 }
8581
8582                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8583                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8584                     DisplayMove(currentMove - 1);
8585                 }
8586                 return;
8587
8588             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8589                 /* crafty (9.25+) says "(only move) <move>"
8590                  * if there is only 1 legal move
8591                  */
8592                 sscanf(p, "(only move) %s", buf1);
8593                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8594                 sprintf(programStats.movelist, "%s (only move)", buf1);
8595                 programStats.depth = 1;
8596                 programStats.nr_moves = 1;
8597                 programStats.moves_left = 1;
8598                 programStats.nodes = 1;
8599                 programStats.time = 1;
8600                 programStats.got_only_move = 1;
8601
8602                 /* Not really, but we also use this member to
8603                    mean "line isn't going to change" (Crafty
8604                    isn't searching, so stats won't change) */
8605                 programStats.line_is_book = 1;
8606
8607                 SendProgramStatsToFrontend( cps, &programStats );
8608
8609                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8610                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8611                     DisplayMove(currentMove - 1);
8612                 }
8613                 return;
8614             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8615                               &time, &nodes, &plylev, &mvleft,
8616                               &mvtot, mvname) >= 5) {
8617                 /* The stat01: line is from Crafty (9.29+) in response
8618                    to the "." command */
8619                 programStats.seen_stat = 1;
8620                 cps->maybeThinking = TRUE;
8621
8622                 if (programStats.got_only_move || !appData.periodicUpdates)
8623                   return;
8624
8625                 programStats.depth = plylev;
8626                 programStats.time = time;
8627                 programStats.nodes = nodes;
8628                 programStats.moves_left = mvleft;
8629                 programStats.nr_moves = mvtot;
8630                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8631                 programStats.ok_to_send = 1;
8632                 programStats.movelist[0] = '\0';
8633
8634                 SendProgramStatsToFrontend( cps, &programStats );
8635
8636                 return;
8637
8638             } else if (strncmp(message,"++",2) == 0) {
8639                 /* Crafty 9.29+ outputs this */
8640                 programStats.got_fail = 2;
8641                 return;
8642
8643             } else if (strncmp(message,"--",2) == 0) {
8644                 /* Crafty 9.29+ outputs this */
8645                 programStats.got_fail = 1;
8646                 return;
8647
8648             } else if (thinkOutput[0] != NULLCHAR &&
8649                        strncmp(message, "    ", 4) == 0) {
8650                 unsigned message_len;
8651
8652                 p = message;
8653                 while (*p && *p == ' ') p++;
8654
8655                 message_len = strlen( p );
8656
8657                 /* [AS] Avoid buffer overflow */
8658                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8659                     strcat(thinkOutput, " ");
8660                     strcat(thinkOutput, p);
8661                 }
8662
8663                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8664                     strcat(programStats.movelist, " ");
8665                     strcat(programStats.movelist, p);
8666                 }
8667
8668                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8669                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8670                     DisplayMove(currentMove - 1);
8671                 }
8672                 return;
8673             }
8674         }
8675         else {
8676             buf1[0] = NULLCHAR;
8677
8678             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8679                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8680             {
8681                 ChessProgramStats cpstats;
8682
8683                 if (plyext != ' ' && plyext != '\t') {
8684                     time *= 100;
8685                 }
8686
8687                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8688                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8689                     curscore = -curscore;
8690                 }
8691
8692                 cpstats.depth = plylev;
8693                 cpstats.nodes = nodes;
8694                 cpstats.time = time;
8695                 cpstats.score = curscore;
8696                 cpstats.got_only_move = 0;
8697                 cpstats.movelist[0] = '\0';
8698
8699                 if (buf1[0] != NULLCHAR) {
8700                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8701                 }
8702
8703                 cpstats.ok_to_send = 0;
8704                 cpstats.line_is_book = 0;
8705                 cpstats.nr_moves = 0;
8706                 cpstats.moves_left = 0;
8707
8708                 SendProgramStatsToFrontend( cps, &cpstats );
8709             }
8710         }
8711     }
8712 }
8713
8714
8715 /* Parse a game score from the character string "game", and
8716    record it as the history of the current game.  The game
8717    score is NOT assumed to start from the standard position.
8718    The display is not updated in any way.
8719    */
8720 void
8721 ParseGameHistory(game)
8722      char *game;
8723 {
8724     ChessMove moveType;
8725     int fromX, fromY, toX, toY, boardIndex;
8726     char promoChar;
8727     char *p, *q;
8728     char buf[MSG_SIZ];
8729
8730     if (appData.debugMode)
8731       fprintf(debugFP, "Parsing game history: %s\n", game);
8732
8733     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8734     gameInfo.site = StrSave(appData.icsHost);
8735     gameInfo.date = PGNDate();
8736     gameInfo.round = StrSave("-");
8737
8738     /* Parse out names of players */
8739     while (*game == ' ') game++;
8740     p = buf;
8741     while (*game != ' ') *p++ = *game++;
8742     *p = NULLCHAR;
8743     gameInfo.white = StrSave(buf);
8744     while (*game == ' ') game++;
8745     p = buf;
8746     while (*game != ' ' && *game != '\n') *p++ = *game++;
8747     *p = NULLCHAR;
8748     gameInfo.black = StrSave(buf);
8749
8750     /* Parse moves */
8751     boardIndex = blackPlaysFirst ? 1 : 0;
8752     yynewstr(game);
8753     for (;;) {
8754         yyboardindex = boardIndex;
8755         moveType = (ChessMove) Myylex();
8756         switch (moveType) {
8757           case IllegalMove:             /* maybe suicide chess, etc. */
8758   if (appData.debugMode) {
8759     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8761     setbuf(debugFP, NULL);
8762   }
8763           case WhitePromotion:
8764           case BlackPromotion:
8765           case WhiteNonPromotion:
8766           case BlackNonPromotion:
8767           case NormalMove:
8768           case WhiteCapturesEnPassant:
8769           case BlackCapturesEnPassant:
8770           case WhiteKingSideCastle:
8771           case WhiteQueenSideCastle:
8772           case BlackKingSideCastle:
8773           case BlackQueenSideCastle:
8774           case WhiteKingSideCastleWild:
8775           case WhiteQueenSideCastleWild:
8776           case BlackKingSideCastleWild:
8777           case BlackQueenSideCastleWild:
8778           /* PUSH Fabien */
8779           case WhiteHSideCastleFR:
8780           case WhiteASideCastleFR:
8781           case BlackHSideCastleFR:
8782           case BlackASideCastleFR:
8783           /* POP Fabien */
8784             fromX = currentMoveString[0] - AAA;
8785             fromY = currentMoveString[1] - ONE;
8786             toX = currentMoveString[2] - AAA;
8787             toY = currentMoveString[3] - ONE;
8788             promoChar = currentMoveString[4];
8789             break;
8790           case WhiteDrop:
8791           case BlackDrop:
8792             fromX = moveType == WhiteDrop ?
8793               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8794             (int) CharToPiece(ToLower(currentMoveString[0]));
8795             fromY = DROP_RANK;
8796             toX = currentMoveString[2] - AAA;
8797             toY = currentMoveString[3] - ONE;
8798             promoChar = NULLCHAR;
8799             break;
8800           case AmbiguousMove:
8801             /* bug? */
8802             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8803   if (appData.debugMode) {
8804     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8805     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8806     setbuf(debugFP, NULL);
8807   }
8808             DisplayError(buf, 0);
8809             return;
8810           case ImpossibleMove:
8811             /* bug? */
8812             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8813   if (appData.debugMode) {
8814     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8815     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8816     setbuf(debugFP, NULL);
8817   }
8818             DisplayError(buf, 0);
8819             return;
8820           case EndOfFile:
8821             if (boardIndex < backwardMostMove) {
8822                 /* Oops, gap.  How did that happen? */
8823                 DisplayError(_("Gap in move list"), 0);
8824                 return;
8825             }
8826             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8827             if (boardIndex > forwardMostMove) {
8828                 forwardMostMove = boardIndex;
8829             }
8830             return;
8831           case ElapsedTime:
8832             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8833                 strcat(parseList[boardIndex-1], " ");
8834                 strcat(parseList[boardIndex-1], yy_text);
8835             }
8836             continue;
8837           case Comment:
8838           case PGNTag:
8839           case NAG:
8840           default:
8841             /* ignore */
8842             continue;
8843           case WhiteWins:
8844           case BlackWins:
8845           case GameIsDrawn:
8846           case GameUnfinished:
8847             if (gameMode == IcsExamining) {
8848                 if (boardIndex < backwardMostMove) {
8849                     /* Oops, gap.  How did that happen? */
8850                     return;
8851                 }
8852                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8853                 return;
8854             }
8855             gameInfo.result = moveType;
8856             p = strchr(yy_text, '{');
8857             if (p == NULL) p = strchr(yy_text, '(');
8858             if (p == NULL) {
8859                 p = yy_text;
8860                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8861             } else {
8862                 q = strchr(p, *p == '{' ? '}' : ')');
8863                 if (q != NULL) *q = NULLCHAR;
8864                 p++;
8865             }
8866             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8867             gameInfo.resultDetails = StrSave(p);
8868             continue;
8869         }
8870         if (boardIndex >= forwardMostMove &&
8871             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8872             backwardMostMove = blackPlaysFirst ? 1 : 0;
8873             return;
8874         }
8875         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8876                                  fromY, fromX, toY, toX, promoChar,
8877                                  parseList[boardIndex]);
8878         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8879         /* currentMoveString is set as a side-effect of yylex */
8880         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8881         strcat(moveList[boardIndex], "\n");
8882         boardIndex++;
8883         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8884         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8885           case MT_NONE:
8886           case MT_STALEMATE:
8887           default:
8888             break;
8889           case MT_CHECK:
8890             if(gameInfo.variant != VariantShogi)
8891                 strcat(parseList[boardIndex - 1], "+");
8892             break;
8893           case MT_CHECKMATE:
8894           case MT_STAINMATE:
8895             strcat(parseList[boardIndex - 1], "#");
8896             break;
8897         }
8898     }
8899 }
8900
8901
8902 /* Apply a move to the given board  */
8903 void
8904 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8905      int fromX, fromY, toX, toY;
8906      int promoChar;
8907      Board board;
8908 {
8909   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8910   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8911
8912     /* [HGM] compute & store e.p. status and castling rights for new position */
8913     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8914
8915       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8916       oldEP = (signed char)board[EP_STATUS];
8917       board[EP_STATUS] = EP_NONE;
8918
8919       if( board[toY][toX] != EmptySquare )
8920            board[EP_STATUS] = EP_CAPTURE;
8921
8922   if (fromY == DROP_RANK) {
8923         /* must be first */
8924         piece = board[toY][toX] = (ChessSquare) fromX;
8925   } else {
8926       int i;
8927
8928       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8929            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8930                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8931       } else
8932       if( board[fromY][fromX] == WhitePawn ) {
8933            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8934                board[EP_STATUS] = EP_PAWN_MOVE;
8935            if( toY-fromY==2) {
8936                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8937                         gameInfo.variant != VariantBerolina || toX < fromX)
8938                       board[EP_STATUS] = toX | berolina;
8939                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8940                         gameInfo.variant != VariantBerolina || toX > fromX)
8941                       board[EP_STATUS] = toX;
8942            }
8943       } else
8944       if( board[fromY][fromX] == BlackPawn ) {
8945            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8946                board[EP_STATUS] = EP_PAWN_MOVE;
8947            if( toY-fromY== -2) {
8948                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8949                         gameInfo.variant != VariantBerolina || toX < fromX)
8950                       board[EP_STATUS] = toX | berolina;
8951                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8952                         gameInfo.variant != VariantBerolina || toX > fromX)
8953                       board[EP_STATUS] = toX;
8954            }
8955        }
8956
8957        for(i=0; i<nrCastlingRights; i++) {
8958            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8959               board[CASTLING][i] == toX   && castlingRank[i] == toY
8960              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8961        }
8962
8963      if (fromX == toX && fromY == toY) return;
8964
8965      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8966      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8967      if(gameInfo.variant == VariantKnightmate)
8968          king += (int) WhiteUnicorn - (int) WhiteKing;
8969
8970     /* Code added by Tord: */
8971     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8972     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8973         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8974       board[fromY][fromX] = EmptySquare;
8975       board[toY][toX] = EmptySquare;
8976       if((toX > fromX) != (piece == WhiteRook)) {
8977         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8978       } else {
8979         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8980       }
8981     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8982                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8983       board[fromY][fromX] = EmptySquare;
8984       board[toY][toX] = EmptySquare;
8985       if((toX > fromX) != (piece == BlackRook)) {
8986         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8987       } else {
8988         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8989       }
8990     /* End of code added by Tord */
8991
8992     } else if (board[fromY][fromX] == king
8993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8994         && toY == fromY && toX > fromX+1) {
8995         board[fromY][fromX] = EmptySquare;
8996         board[toY][toX] = king;
8997         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8998         board[fromY][BOARD_RGHT-1] = EmptySquare;
8999     } else if (board[fromY][fromX] == king
9000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9001                && toY == fromY && toX < fromX-1) {
9002         board[fromY][fromX] = EmptySquare;
9003         board[toY][toX] = king;
9004         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9005         board[fromY][BOARD_LEFT] = EmptySquare;
9006     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9007                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9008                && toY >= BOARD_HEIGHT-promoRank
9009                ) {
9010         /* white pawn promotion */
9011         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9012         if (board[toY][toX] == EmptySquare) {
9013             board[toY][toX] = WhiteQueen;
9014         }
9015         if(gameInfo.variant==VariantBughouse ||
9016            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9017             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9018         board[fromY][fromX] = EmptySquare;
9019     } else if ((fromY == BOARD_HEIGHT-4)
9020                && (toX != fromX)
9021                && gameInfo.variant != VariantXiangqi
9022                && gameInfo.variant != VariantBerolina
9023                && (board[fromY][fromX] == WhitePawn)
9024                && (board[toY][toX] == EmptySquare)) {
9025         board[fromY][fromX] = EmptySquare;
9026         board[toY][toX] = WhitePawn;
9027         captured = board[toY - 1][toX];
9028         board[toY - 1][toX] = EmptySquare;
9029     } else if ((fromY == BOARD_HEIGHT-4)
9030                && (toX == fromX)
9031                && gameInfo.variant == VariantBerolina
9032                && (board[fromY][fromX] == WhitePawn)
9033                && (board[toY][toX] == EmptySquare)) {
9034         board[fromY][fromX] = EmptySquare;
9035         board[toY][toX] = WhitePawn;
9036         if(oldEP & EP_BEROLIN_A) {
9037                 captured = board[fromY][fromX-1];
9038                 board[fromY][fromX-1] = EmptySquare;
9039         }else{  captured = board[fromY][fromX+1];
9040                 board[fromY][fromX+1] = EmptySquare;
9041         }
9042     } else if (board[fromY][fromX] == king
9043         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9044                && toY == fromY && toX > fromX+1) {
9045         board[fromY][fromX] = EmptySquare;
9046         board[toY][toX] = king;
9047         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9048         board[fromY][BOARD_RGHT-1] = EmptySquare;
9049     } else if (board[fromY][fromX] == king
9050         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9051                && toY == fromY && toX < fromX-1) {
9052         board[fromY][fromX] = EmptySquare;
9053         board[toY][toX] = king;
9054         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9055         board[fromY][BOARD_LEFT] = EmptySquare;
9056     } else if (fromY == 7 && fromX == 3
9057                && board[fromY][fromX] == BlackKing
9058                && toY == 7 && toX == 5) {
9059         board[fromY][fromX] = EmptySquare;
9060         board[toY][toX] = BlackKing;
9061         board[fromY][7] = EmptySquare;
9062         board[toY][4] = BlackRook;
9063     } else if (fromY == 7 && fromX == 3
9064                && board[fromY][fromX] == BlackKing
9065                && toY == 7 && toX == 1) {
9066         board[fromY][fromX] = EmptySquare;
9067         board[toY][toX] = BlackKing;
9068         board[fromY][0] = EmptySquare;
9069         board[toY][2] = BlackRook;
9070     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9071                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9072                && toY < promoRank
9073                ) {
9074         /* black pawn promotion */
9075         board[toY][toX] = CharToPiece(ToLower(promoChar));
9076         if (board[toY][toX] == EmptySquare) {
9077             board[toY][toX] = BlackQueen;
9078         }
9079         if(gameInfo.variant==VariantBughouse ||
9080            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9081             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9082         board[fromY][fromX] = EmptySquare;
9083     } else if ((fromY == 3)
9084                && (toX != fromX)
9085                && gameInfo.variant != VariantXiangqi
9086                && gameInfo.variant != VariantBerolina
9087                && (board[fromY][fromX] == BlackPawn)
9088                && (board[toY][toX] == EmptySquare)) {
9089         board[fromY][fromX] = EmptySquare;
9090         board[toY][toX] = BlackPawn;
9091         captured = board[toY + 1][toX];
9092         board[toY + 1][toX] = EmptySquare;
9093     } else if ((fromY == 3)
9094                && (toX == fromX)
9095                && gameInfo.variant == VariantBerolina
9096                && (board[fromY][fromX] == BlackPawn)
9097                && (board[toY][toX] == EmptySquare)) {
9098         board[fromY][fromX] = EmptySquare;
9099         board[toY][toX] = BlackPawn;
9100         if(oldEP & EP_BEROLIN_A) {
9101                 captured = board[fromY][fromX-1];
9102                 board[fromY][fromX-1] = EmptySquare;
9103         }else{  captured = board[fromY][fromX+1];
9104                 board[fromY][fromX+1] = EmptySquare;
9105         }
9106     } else {
9107         board[toY][toX] = board[fromY][fromX];
9108         board[fromY][fromX] = EmptySquare;
9109     }
9110   }
9111
9112     if (gameInfo.holdingsWidth != 0) {
9113
9114       /* !!A lot more code needs to be written to support holdings  */
9115       /* [HGM] OK, so I have written it. Holdings are stored in the */
9116       /* penultimate board files, so they are automaticlly stored   */
9117       /* in the game history.                                       */
9118       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9119                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9120         /* Delete from holdings, by decreasing count */
9121         /* and erasing image if necessary            */
9122         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9123         if(p < (int) BlackPawn) { /* white drop */
9124              p -= (int)WhitePawn;
9125                  p = PieceToNumber((ChessSquare)p);
9126              if(p >= gameInfo.holdingsSize) p = 0;
9127              if(--board[p][BOARD_WIDTH-2] <= 0)
9128                   board[p][BOARD_WIDTH-1] = EmptySquare;
9129              if((int)board[p][BOARD_WIDTH-2] < 0)
9130                         board[p][BOARD_WIDTH-2] = 0;
9131         } else {                  /* black drop */
9132              p -= (int)BlackPawn;
9133                  p = PieceToNumber((ChessSquare)p);
9134              if(p >= gameInfo.holdingsSize) p = 0;
9135              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9136                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9137              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9138                         board[BOARD_HEIGHT-1-p][1] = 0;
9139         }
9140       }
9141       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9142           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9143         /* [HGM] holdings: Add to holdings, if holdings exist */
9144         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9145                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9146                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9147         }
9148         p = (int) captured;
9149         if (p >= (int) BlackPawn) {
9150           p -= (int)BlackPawn;
9151           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9152                   /* in Shogi restore piece to its original  first */
9153                   captured = (ChessSquare) (DEMOTED captured);
9154                   p = DEMOTED p;
9155           }
9156           p = PieceToNumber((ChessSquare)p);
9157           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9158           board[p][BOARD_WIDTH-2]++;
9159           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9160         } else {
9161           p -= (int)WhitePawn;
9162           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9163                   captured = (ChessSquare) (DEMOTED captured);
9164                   p = DEMOTED p;
9165           }
9166           p = PieceToNumber((ChessSquare)p);
9167           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9168           board[BOARD_HEIGHT-1-p][1]++;
9169           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9170         }
9171       }
9172     } else if (gameInfo.variant == VariantAtomic) {
9173       if (captured != EmptySquare) {
9174         int y, x;
9175         for (y = toY-1; y <= toY+1; y++) {
9176           for (x = toX-1; x <= toX+1; x++) {
9177             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9178                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9179               board[y][x] = EmptySquare;
9180             }
9181           }
9182         }
9183         board[toY][toX] = EmptySquare;
9184       }
9185     }
9186     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9187         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9188     } else
9189     if(promoChar == '+') {
9190         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9191         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9192     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9193         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9194     }
9195     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9196                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9197         // [HGM] superchess: take promotion piece out of holdings
9198         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9199         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9200             if(!--board[k][BOARD_WIDTH-2])
9201                 board[k][BOARD_WIDTH-1] = EmptySquare;
9202         } else {
9203             if(!--board[BOARD_HEIGHT-1-k][1])
9204                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9205         }
9206     }
9207
9208 }
9209
9210 /* Updates forwardMostMove */
9211 void
9212 MakeMove(fromX, fromY, toX, toY, promoChar)
9213      int fromX, fromY, toX, toY;
9214      int promoChar;
9215 {
9216 //    forwardMostMove++; // [HGM] bare: moved downstream
9217
9218     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9219         int timeLeft; static int lastLoadFlag=0; int king, piece;
9220         piece = boards[forwardMostMove][fromY][fromX];
9221         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9222         if(gameInfo.variant == VariantKnightmate)
9223             king += (int) WhiteUnicorn - (int) WhiteKing;
9224         if(forwardMostMove == 0) {
9225             if(blackPlaysFirst)
9226                 fprintf(serverMoves, "%s;", second.tidy);
9227             fprintf(serverMoves, "%s;", first.tidy);
9228             if(!blackPlaysFirst)
9229                 fprintf(serverMoves, "%s;", second.tidy);
9230         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9231         lastLoadFlag = loadFlag;
9232         // print base move
9233         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9234         // print castling suffix
9235         if( toY == fromY && piece == king ) {
9236             if(toX-fromX > 1)
9237                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9238             if(fromX-toX >1)
9239                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9240         }
9241         // e.p. suffix
9242         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9243              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9244              boards[forwardMostMove][toY][toX] == EmptySquare
9245              && fromX != toX && fromY != toY)
9246                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9247         // promotion suffix
9248         if(promoChar != NULLCHAR)
9249                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9250         if(!loadFlag) {
9251             fprintf(serverMoves, "/%d/%d",
9252                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9253             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9254             else                      timeLeft = blackTimeRemaining/1000;
9255             fprintf(serverMoves, "/%d", timeLeft);
9256         }
9257         fflush(serverMoves);
9258     }
9259
9260     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9261       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9262                         0, 1);
9263       return;
9264     }
9265     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9266     if (commentList[forwardMostMove+1] != NULL) {
9267         free(commentList[forwardMostMove+1]);
9268         commentList[forwardMostMove+1] = NULL;
9269     }
9270     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9271     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9272     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9273     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9274     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9275     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9276     gameInfo.result = GameUnfinished;
9277     if (gameInfo.resultDetails != NULL) {
9278         free(gameInfo.resultDetails);
9279         gameInfo.resultDetails = NULL;
9280     }
9281     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9282                               moveList[forwardMostMove - 1]);
9283     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9284                              PosFlags(forwardMostMove - 1),
9285                              fromY, fromX, toY, toX, promoChar,
9286                              parseList[forwardMostMove - 1]);
9287     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9288       case MT_NONE:
9289       case MT_STALEMATE:
9290       default:
9291         break;
9292       case MT_CHECK:
9293         if(gameInfo.variant != VariantShogi)
9294             strcat(parseList[forwardMostMove - 1], "+");
9295         break;
9296       case MT_CHECKMATE:
9297       case MT_STAINMATE:
9298         strcat(parseList[forwardMostMove - 1], "#");
9299         break;
9300     }
9301     if (appData.debugMode) {
9302         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9303     }
9304
9305 }
9306
9307 /* Updates currentMove if not pausing */
9308 void
9309 ShowMove(fromX, fromY, toX, toY)
9310 {
9311     int instant = (gameMode == PlayFromGameFile) ?
9312         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9313     if(appData.noGUI) return;
9314     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9315         if (!instant) {
9316             if (forwardMostMove == currentMove + 1) {
9317                 AnimateMove(boards[forwardMostMove - 1],
9318                             fromX, fromY, toX, toY);
9319             }
9320             if (appData.highlightLastMove) {
9321                 SetHighlights(fromX, fromY, toX, toY);
9322             }
9323         }
9324         currentMove = forwardMostMove;
9325     }
9326
9327     if (instant) return;
9328
9329     DisplayMove(currentMove - 1);
9330     DrawPosition(FALSE, boards[currentMove]);
9331     DisplayBothClocks();
9332     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9333     DisplayBook(currentMove);
9334 }
9335
9336 void SendEgtPath(ChessProgramState *cps)
9337 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9338         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9339
9340         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9341
9342         while(*p) {
9343             char c, *q = name+1, *r, *s;
9344
9345             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9346             while(*p && *p != ',') *q++ = *p++;
9347             *q++ = ':'; *q = 0;
9348             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9349                 strcmp(name, ",nalimov:") == 0 ) {
9350                 // take nalimov path from the menu-changeable option first, if it is defined
9351               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9352                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9353             } else
9354             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9355                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9356                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9357                 s = r = StrStr(s, ":") + 1; // beginning of path info
9358                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9359                 c = *r; *r = 0;             // temporarily null-terminate path info
9360                     *--q = 0;               // strip of trailig ':' from name
9361                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9362                 *r = c;
9363                 SendToProgram(buf,cps);     // send egtbpath command for this format
9364             }
9365             if(*p == ',') p++; // read away comma to position for next format name
9366         }
9367 }
9368
9369 void
9370 InitChessProgram(cps, setup)
9371      ChessProgramState *cps;
9372      int setup; /* [HGM] needed to setup FRC opening position */
9373 {
9374     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9375     if (appData.noChessProgram) return;
9376     hintRequested = FALSE;
9377     bookRequested = FALSE;
9378
9379     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9380     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9381     if(cps->memSize) { /* [HGM] memory */
9382       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9383         SendToProgram(buf, cps);
9384     }
9385     SendEgtPath(cps); /* [HGM] EGT */
9386     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9387       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9388         SendToProgram(buf, cps);
9389     }
9390
9391     SendToProgram(cps->initString, cps);
9392     if (gameInfo.variant != VariantNormal &&
9393         gameInfo.variant != VariantLoadable
9394         /* [HGM] also send variant if board size non-standard */
9395         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9396                                             ) {
9397       char *v = VariantName(gameInfo.variant);
9398       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9399         /* [HGM] in protocol 1 we have to assume all variants valid */
9400         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9401         DisplayFatalError(buf, 0, 1);
9402         return;
9403       }
9404
9405       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9406       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9407       if( gameInfo.variant == VariantXiangqi )
9408            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9409       if( gameInfo.variant == VariantShogi )
9410            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9411       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9412            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9413       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9414           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9415            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9416       if( gameInfo.variant == VariantCourier )
9417            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9418       if( gameInfo.variant == VariantSuper )
9419            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9420       if( gameInfo.variant == VariantGreat )
9421            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9422       if( gameInfo.variant == VariantSChess )
9423            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9424
9425       if(overruled) {
9426         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9427                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9428            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9429            if(StrStr(cps->variants, b) == NULL) {
9430                // specific sized variant not known, check if general sizing allowed
9431                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9432                    if(StrStr(cps->variants, "boardsize") == NULL) {
9433                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9434                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9435                        DisplayFatalError(buf, 0, 1);
9436                        return;
9437                    }
9438                    /* [HGM] here we really should compare with the maximum supported board size */
9439                }
9440            }
9441       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9442       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9443       SendToProgram(buf, cps);
9444     }
9445     currentlyInitializedVariant = gameInfo.variant;
9446
9447     /* [HGM] send opening position in FRC to first engine */
9448     if(setup) {
9449           SendToProgram("force\n", cps);
9450           SendBoard(cps, 0);
9451           /* engine is now in force mode! Set flag to wake it up after first move. */
9452           setboardSpoiledMachineBlack = 1;
9453     }
9454
9455     if (cps->sendICS) {
9456       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9457       SendToProgram(buf, cps);
9458     }
9459     cps->maybeThinking = FALSE;
9460     cps->offeredDraw = 0;
9461     if (!appData.icsActive) {
9462         SendTimeControl(cps, movesPerSession, timeControl,
9463                         timeIncrement, appData.searchDepth,
9464                         searchTime);
9465     }
9466     if (appData.showThinking
9467         // [HGM] thinking: four options require thinking output to be sent
9468         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9469                                 ) {
9470         SendToProgram("post\n", cps);
9471     }
9472     SendToProgram("hard\n", cps);
9473     if (!appData.ponderNextMove) {
9474         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9475            it without being sure what state we are in first.  "hard"
9476            is not a toggle, so that one is OK.
9477          */
9478         SendToProgram("easy\n", cps);
9479     }
9480     if (cps->usePing) {
9481       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9482       SendToProgram(buf, cps);
9483     }
9484     cps->initDone = TRUE;
9485 }
9486
9487
9488 void
9489 StartChessProgram(cps)
9490      ChessProgramState *cps;
9491 {
9492     char buf[MSG_SIZ];
9493     int err;
9494
9495     if (appData.noChessProgram) return;
9496     cps->initDone = FALSE;
9497
9498     if (strcmp(cps->host, "localhost") == 0) {
9499         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9500     } else if (*appData.remoteShell == NULLCHAR) {
9501         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9502     } else {
9503         if (*appData.remoteUser == NULLCHAR) {
9504           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9505                     cps->program);
9506         } else {
9507           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9508                     cps->host, appData.remoteUser, cps->program);
9509         }
9510         err = StartChildProcess(buf, "", &cps->pr);
9511     }
9512
9513     if (err != 0) {
9514       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9515         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9516         if(cps != &first) return;
9517         appData.noChessProgram = TRUE;
9518         ThawUI();
9519         SetNCPMode();
9520 //      DisplayFatalError(buf, err, 1);
9521 //      cps->pr = NoProc;
9522 //      cps->isr = NULL;
9523         return;
9524     }
9525
9526     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9527     if (cps->protocolVersion > 1) {
9528       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9529       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9530       cps->comboCnt = 0;  //                and values of combo boxes
9531       SendToProgram(buf, cps);
9532     } else {
9533       SendToProgram("xboard\n", cps);
9534     }
9535 }
9536
9537 void
9538 TwoMachinesEventIfReady P((void))
9539 {
9540   static int curMess = 0;
9541   if (first.lastPing != first.lastPong) {
9542     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9543     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9544     return;
9545   }
9546   if (second.lastPing != second.lastPong) {
9547     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9548     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9549     return;
9550   }
9551   DisplayMessage("", ""); curMess = 0;
9552   ThawUI();
9553   TwoMachinesEvent();
9554 }
9555
9556 int
9557 CountPlayers(char *p)
9558 {
9559     int n = 0;
9560     while(p = strchr(p, '\n')) p++, n++; // count participants
9561     return n;
9562 }
9563
9564 FILE *
9565 WriteTourneyFile(char *results)
9566 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9567     FILE *f = fopen(appData.tourneyFile, "w");
9568     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9569         // create a file with tournament description
9570         fprintf(f, "-participants {%s}\n", appData.participants);
9571         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9572         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9573         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9574         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9575         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9576         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9577         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9578         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9579         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9580         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9581         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9582         if(searchTime > 0)
9583                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9584         else {
9585                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9586                 fprintf(f, "-tc %s\n", appData.timeControl);
9587                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9588         }
9589         fprintf(f, "-results \"%s\"\n", results);
9590     }
9591     return f;
9592 }
9593
9594 int
9595 CreateTourney(char *name)
9596 {
9597         FILE *f;
9598         if(name[0] == NULLCHAR) {
9599             DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9600             return 0;
9601         }
9602         f = fopen(appData.tourneyFile, "r");
9603         if(f) { // file exists
9604             ParseArgsFromFile(f); // parse it
9605         } else {
9606             if(CountPlayers(appData.participants) < appData.tourneyType + (!appData.tourneyType) + 1) {
9607                 DisplayError(_("Not enough participants"), 0);
9608                 return 0;
9609             }
9610             if((f = WriteTourneyFile("")) == NULL) return 0;
9611         }
9612         fclose(f);
9613         appData.noChessProgram = FALSE;
9614         appData.clockMode = TRUE;
9615         SetGNUMode();
9616         return 1;
9617 }
9618
9619 #define MAXENGINES 1000
9620 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9621
9622 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9623 {
9624     char buf[MSG_SIZ], *p, *q;
9625     int i=1;
9626     while(*names) {
9627         p = names; q = buf;
9628         while(*p && *p != '\n') *q++ = *p++;
9629         *q = 0;
9630         if(engineList[i]) free(engineList[i]);
9631         engineList[i] = strdup(buf);
9632         if(*p == '\n') p++;
9633         TidyProgramName(engineList[i], "localhost", buf);
9634         if(engineMnemonic[i]) free(engineMnemonic[i]);
9635         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9636             strcat(buf, " (");
9637             sscanf(q + 8, "%s", buf + strlen(buf));
9638             strcat(buf, ")");
9639         }
9640         engineMnemonic[i] = strdup(buf);
9641         names = p; i++;
9642       if(i > MAXENGINES - 2) break;
9643     }
9644     engineList[i] = NULL;
9645 }
9646
9647 // following implemented as macro to avoid type limitations
9648 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9649
9650 void SwapEngines(int n)
9651 {   // swap settings for first engine and other engine (so far only some selected options)
9652     int h;
9653     char *p;
9654     if(n == 0) return;
9655     SWAP(directory, p)
9656     SWAP(chessProgram, p)
9657     SWAP(isUCI, h)
9658     SWAP(hasOwnBookUCI, h)
9659     SWAP(protocolVersion, h)
9660     SWAP(reuse, h)
9661     SWAP(scoreIsAbsolute, h)
9662     SWAP(timeOdds, h)
9663     SWAP(logo, p)
9664     SWAP(pgnName, p)
9665 }
9666
9667 void
9668 SetPlayer(int player)
9669 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9670     int i;
9671     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9672     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9673     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9674     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9675     if(mnemonic[i]) {
9676         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9677         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9678         ParseArgsFromString(buf);
9679     }
9680     free(engineName);
9681 }
9682
9683 int
9684 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9685 {   // determine players from game number
9686     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9687
9688     if(appData.tourneyType == 0) {
9689         roundsPerCycle = (nPlayers - 1) | 1;
9690         pairingsPerRound = nPlayers / 2;
9691     } else if(appData.tourneyType > 0) {
9692         roundsPerCycle = nPlayers - appData.tourneyType;
9693         pairingsPerRound = appData.tourneyType;
9694     }
9695     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9696     gamesPerCycle = gamesPerRound * roundsPerCycle;
9697     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9698     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9699     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9700     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9701     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9702     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9703
9704     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9705     if(appData.roundSync) *syncInterval = gamesPerRound;
9706
9707     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9708
9709     if(appData.tourneyType == 0) {
9710         if(curPairing == (nPlayers-1)/2 ) {
9711             *whitePlayer = curRound;
9712             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9713         } else {
9714             *whitePlayer = curRound - pairingsPerRound + curPairing;
9715             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9716             *blackPlayer = curRound + pairingsPerRound - curPairing;
9717             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9718         }
9719     } else if(appData.tourneyType > 0) {
9720         *whitePlayer = curPairing;
9721         *blackPlayer = curRound + appData.tourneyType;
9722     }
9723
9724     // take care of white/black alternation per round. 
9725     // For cycles and games this is already taken care of by default, derived from matchGame!
9726     return curRound & 1;
9727 }
9728
9729 int
9730 NextTourneyGame(int nr, int *swapColors)
9731 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9732     char *p, *q;
9733     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9734     FILE *tf;
9735     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9736     tf = fopen(appData.tourneyFile, "r");
9737     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9738     ParseArgsFromFile(tf); fclose(tf);
9739     InitTimeControls(); // TC might be altered from tourney file
9740
9741     nPlayers = CountPlayers(appData.participants); // count participants
9742     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9743         if(nr>=0 && !pairingReceived) {
9744             char buf[1<<16];
9745             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9746             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9747             SendToProgram(buf, &pairing);
9748             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9749             SendToProgram(buf, &pairing);
9750             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9751         }
9752         pairingReceived = 0;                              // ... so we continue here 
9753         syncInterval = nPlayers/2; *swapColors = 0;
9754         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9755         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9756         matchGame = 1; roundNr = nr / syncInterval + 1;
9757     } else
9758     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9759
9760     if(syncInterval) {
9761         p = q = appData.results;
9762         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9763         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9764             DisplayMessage(_("Waiting for other game(s)"),"");
9765             waitingForGame = TRUE;
9766             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9767             return 0;
9768         }
9769         waitingForGame = FALSE;
9770     }
9771
9772     if(first.pr != NoProc) return 1; // engines already loaded
9773
9774     // redefine engines, engine dir, etc.
9775     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9776     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9777     SwapEngines(1);
9778     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9779     SwapEngines(1);         // and make that valid for second engine by swapping
9780     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9781     InitEngine(&second, 1);
9782     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9783     return 1;
9784 }
9785
9786 void
9787 NextMatchGame()
9788 {   // performs game initialization that does not invoke engines, and then tries to start the game
9789     int firstWhite, swapColors = 0;
9790     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9791     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9792     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9793     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9794     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9795     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9796     Reset(FALSE, first.pr != NoProc);
9797     appData.noChessProgram = FALSE;
9798     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9799     TwoMachinesEvent();
9800 }
9801
9802 void UserAdjudicationEvent( int result )
9803 {
9804     ChessMove gameResult = GameIsDrawn;
9805
9806     if( result > 0 ) {
9807         gameResult = WhiteWins;
9808     }
9809     else if( result < 0 ) {
9810         gameResult = BlackWins;
9811     }
9812
9813     if( gameMode == TwoMachinesPlay ) {
9814         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9815     }
9816 }
9817
9818
9819 // [HGM] save: calculate checksum of game to make games easily identifiable
9820 int StringCheckSum(char *s)
9821 {
9822         int i = 0;
9823         if(s==NULL) return 0;
9824         while(*s) i = i*259 + *s++;
9825         return i;
9826 }
9827
9828 int GameCheckSum()
9829 {
9830         int i, sum=0;
9831         for(i=backwardMostMove; i<forwardMostMove; i++) {
9832                 sum += pvInfoList[i].depth;
9833                 sum += StringCheckSum(parseList[i]);
9834                 sum += StringCheckSum(commentList[i]);
9835                 sum *= 261;
9836         }
9837         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9838         return sum + StringCheckSum(commentList[i]);
9839 } // end of save patch
9840
9841 void
9842 GameEnds(result, resultDetails, whosays)
9843      ChessMove result;
9844      char *resultDetails;
9845      int whosays;
9846 {
9847     GameMode nextGameMode;
9848     int isIcsGame;
9849     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9850
9851     if(endingGame) return; /* [HGM] crash: forbid recursion */
9852     endingGame = 1;
9853     if(twoBoards) { // [HGM] dual: switch back to one board
9854         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9855         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9856     }
9857     if (appData.debugMode) {
9858       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9859               result, resultDetails ? resultDetails : "(null)", whosays);
9860     }
9861
9862     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9863
9864     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9865         /* If we are playing on ICS, the server decides when the
9866            game is over, but the engine can offer to draw, claim
9867            a draw, or resign.
9868          */
9869 #if ZIPPY
9870         if (appData.zippyPlay && first.initDone) {
9871             if (result == GameIsDrawn) {
9872                 /* In case draw still needs to be claimed */
9873                 SendToICS(ics_prefix);
9874                 SendToICS("draw\n");
9875             } else if (StrCaseStr(resultDetails, "resign")) {
9876                 SendToICS(ics_prefix);
9877                 SendToICS("resign\n");
9878             }
9879         }
9880 #endif
9881         endingGame = 0; /* [HGM] crash */
9882         return;
9883     }
9884
9885     /* If we're loading the game from a file, stop */
9886     if (whosays == GE_FILE) {
9887       (void) StopLoadGameTimer();
9888       gameFileFP = NULL;
9889     }
9890
9891     /* Cancel draw offers */
9892     first.offeredDraw = second.offeredDraw = 0;
9893
9894     /* If this is an ICS game, only ICS can really say it's done;
9895        if not, anyone can. */
9896     isIcsGame = (gameMode == IcsPlayingWhite ||
9897                  gameMode == IcsPlayingBlack ||
9898                  gameMode == IcsObserving    ||
9899                  gameMode == IcsExamining);
9900
9901     if (!isIcsGame || whosays == GE_ICS) {
9902         /* OK -- not an ICS game, or ICS said it was done */
9903         StopClocks();
9904         if (!isIcsGame && !appData.noChessProgram)
9905           SetUserThinkingEnables();
9906
9907         /* [HGM] if a machine claims the game end we verify this claim */
9908         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9909             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9910                 char claimer;
9911                 ChessMove trueResult = (ChessMove) -1;
9912
9913                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9914                                             first.twoMachinesColor[0] :
9915                                             second.twoMachinesColor[0] ;
9916
9917                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9918                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9919                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9920                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9921                 } else
9922                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9923                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9924                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9925                 } else
9926                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9927                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9928                 }
9929
9930                 // now verify win claims, but not in drop games, as we don't understand those yet
9931                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9932                                                  || gameInfo.variant == VariantGreat) &&
9933                     (result == WhiteWins && claimer == 'w' ||
9934                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9935                       if (appData.debugMode) {
9936                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9937                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9938                       }
9939                       if(result != trueResult) {
9940                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9941                               result = claimer == 'w' ? BlackWins : WhiteWins;
9942                               resultDetails = buf;
9943                       }
9944                 } else
9945                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9946                     && (forwardMostMove <= backwardMostMove ||
9947                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9948                         (claimer=='b')==(forwardMostMove&1))
9949                                                                                   ) {
9950                       /* [HGM] verify: draws that were not flagged are false claims */
9951                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9952                       result = claimer == 'w' ? BlackWins : WhiteWins;
9953                       resultDetails = buf;
9954                 }
9955                 /* (Claiming a loss is accepted no questions asked!) */
9956             }
9957             /* [HGM] bare: don't allow bare King to win */
9958             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9959                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9960                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9961                && result != GameIsDrawn)
9962             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9963                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9964                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9965                         if(p >= 0 && p <= (int)WhiteKing) k++;
9966                 }
9967                 if (appData.debugMode) {
9968                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9969                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9970                 }
9971                 if(k <= 1) {
9972                         result = GameIsDrawn;
9973                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9974                         resultDetails = buf;
9975                 }
9976             }
9977         }
9978
9979
9980         if(serverMoves != NULL && !loadFlag) { char c = '=';
9981             if(result==WhiteWins) c = '+';
9982             if(result==BlackWins) c = '-';
9983             if(resultDetails != NULL)
9984                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9985         }
9986         if (resultDetails != NULL) {
9987             gameInfo.result = result;
9988             gameInfo.resultDetails = StrSave(resultDetails);
9989
9990             /* display last move only if game was not loaded from file */
9991             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9992                 DisplayMove(currentMove - 1);
9993
9994             if (forwardMostMove != 0) {
9995                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9996                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9997                                                                 ) {
9998                     if (*appData.saveGameFile != NULLCHAR) {
9999                         SaveGameToFile(appData.saveGameFile, TRUE);
10000                     } else if (appData.autoSaveGames) {
10001                         AutoSaveGame();
10002                     }
10003                     if (*appData.savePositionFile != NULLCHAR) {
10004                         SavePositionToFile(appData.savePositionFile);
10005                     }
10006                 }
10007             }
10008
10009             /* Tell program how game ended in case it is learning */
10010             /* [HGM] Moved this to after saving the PGN, just in case */
10011             /* engine died and we got here through time loss. In that */
10012             /* case we will get a fatal error writing the pipe, which */
10013             /* would otherwise lose us the PGN.                       */
10014             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10015             /* output during GameEnds should never be fatal anymore   */
10016             if (gameMode == MachinePlaysWhite ||
10017                 gameMode == MachinePlaysBlack ||
10018                 gameMode == TwoMachinesPlay ||
10019                 gameMode == IcsPlayingWhite ||
10020                 gameMode == IcsPlayingBlack ||
10021                 gameMode == BeginningOfGame) {
10022                 char buf[MSG_SIZ];
10023                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10024                         resultDetails);
10025                 if (first.pr != NoProc) {
10026                     SendToProgram(buf, &first);
10027                 }
10028                 if (second.pr != NoProc &&
10029                     gameMode == TwoMachinesPlay) {
10030                     SendToProgram(buf, &second);
10031                 }
10032             }
10033         }
10034
10035         if (appData.icsActive) {
10036             if (appData.quietPlay &&
10037                 (gameMode == IcsPlayingWhite ||
10038                  gameMode == IcsPlayingBlack)) {
10039                 SendToICS(ics_prefix);
10040                 SendToICS("set shout 1\n");
10041             }
10042             nextGameMode = IcsIdle;
10043             ics_user_moved = FALSE;
10044             /* clean up premove.  It's ugly when the game has ended and the
10045              * premove highlights are still on the board.
10046              */
10047             if (gotPremove) {
10048               gotPremove = FALSE;
10049               ClearPremoveHighlights();
10050               DrawPosition(FALSE, boards[currentMove]);
10051             }
10052             if (whosays == GE_ICS) {
10053                 switch (result) {
10054                 case WhiteWins:
10055                     if (gameMode == IcsPlayingWhite)
10056                         PlayIcsWinSound();
10057                     else if(gameMode == IcsPlayingBlack)
10058                         PlayIcsLossSound();
10059                     break;
10060                 case BlackWins:
10061                     if (gameMode == IcsPlayingBlack)
10062                         PlayIcsWinSound();
10063                     else if(gameMode == IcsPlayingWhite)
10064                         PlayIcsLossSound();
10065                     break;
10066                 case GameIsDrawn:
10067                     PlayIcsDrawSound();
10068                     break;
10069                 default:
10070                     PlayIcsUnfinishedSound();
10071                 }
10072             }
10073         } else if (gameMode == EditGame ||
10074                    gameMode == PlayFromGameFile ||
10075                    gameMode == AnalyzeMode ||
10076                    gameMode == AnalyzeFile) {
10077             nextGameMode = gameMode;
10078         } else {
10079             nextGameMode = EndOfGame;
10080         }
10081         pausing = FALSE;
10082         ModeHighlight();
10083     } else {
10084         nextGameMode = gameMode;
10085     }
10086
10087     if (appData.noChessProgram) {
10088         gameMode = nextGameMode;
10089         ModeHighlight();
10090         endingGame = 0; /* [HGM] crash */
10091         return;
10092     }
10093
10094     if (first.reuse) {
10095         /* Put first chess program into idle state */
10096         if (first.pr != NoProc &&
10097             (gameMode == MachinePlaysWhite ||
10098              gameMode == MachinePlaysBlack ||
10099              gameMode == TwoMachinesPlay ||
10100              gameMode == IcsPlayingWhite ||
10101              gameMode == IcsPlayingBlack ||
10102              gameMode == BeginningOfGame)) {
10103             SendToProgram("force\n", &first);
10104             if (first.usePing) {
10105               char buf[MSG_SIZ];
10106               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10107               SendToProgram(buf, &first);
10108             }
10109         }
10110     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10111         /* Kill off first chess program */
10112         if (first.isr != NULL)
10113           RemoveInputSource(first.isr);
10114         first.isr = NULL;
10115
10116         if (first.pr != NoProc) {
10117             ExitAnalyzeMode();
10118             DoSleep( appData.delayBeforeQuit );
10119             SendToProgram("quit\n", &first);
10120             DoSleep( appData.delayAfterQuit );
10121             DestroyChildProcess(first.pr, first.useSigterm);
10122         }
10123         first.pr = NoProc;
10124     }
10125     if (second.reuse) {
10126         /* Put second chess program into idle state */
10127         if (second.pr != NoProc &&
10128             gameMode == TwoMachinesPlay) {
10129             SendToProgram("force\n", &second);
10130             if (second.usePing) {
10131               char buf[MSG_SIZ];
10132               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10133               SendToProgram(buf, &second);
10134             }
10135         }
10136     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10137         /* Kill off second chess program */
10138         if (second.isr != NULL)
10139           RemoveInputSource(second.isr);
10140         second.isr = NULL;
10141
10142         if (second.pr != NoProc) {
10143             DoSleep( appData.delayBeforeQuit );
10144             SendToProgram("quit\n", &second);
10145             DoSleep( appData.delayAfterQuit );
10146             DestroyChildProcess(second.pr, second.useSigterm);
10147         }
10148         second.pr = NoProc;
10149     }
10150
10151     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10152         char resChar = '=';
10153         switch (result) {
10154         case WhiteWins:
10155           resChar = '+';
10156           if (first.twoMachinesColor[0] == 'w') {
10157             first.matchWins++;
10158           } else {
10159             second.matchWins++;
10160           }
10161           break;
10162         case BlackWins:
10163           resChar = '-';
10164           if (first.twoMachinesColor[0] == 'b') {
10165             first.matchWins++;
10166           } else {
10167             second.matchWins++;
10168           }
10169           break;
10170         case GameUnfinished:
10171           resChar = ' ';
10172         default:
10173           break;
10174         }
10175
10176         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10177         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10178             ReserveGame(nextGame, resChar); // sets nextGame
10179             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10180         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10181
10182         if (nextGame <= appData.matchGames && !abortMatch) {
10183             gameMode = nextGameMode;
10184             matchGame = nextGame; // this will be overruled in tourney mode!
10185             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10186             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10187             endingGame = 0; /* [HGM] crash */
10188             return;
10189         } else {
10190             gameMode = nextGameMode;
10191             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10192                      first.tidy, second.tidy,
10193                      first.matchWins, second.matchWins,
10194                      appData.matchGames - (first.matchWins + second.matchWins));
10195             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10196             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10197                 first.twoMachinesColor = "black\n";
10198                 second.twoMachinesColor = "white\n";
10199             } else {
10200                 first.twoMachinesColor = "white\n";
10201                 second.twoMachinesColor = "black\n";
10202             }
10203         }
10204     }
10205     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10206         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10207       ExitAnalyzeMode();
10208     gameMode = nextGameMode;
10209     ModeHighlight();
10210     endingGame = 0;  /* [HGM] crash */
10211     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10212         if(matchMode == TRUE) { // match through command line: exit with or without popup
10213             if(ranking) {
10214                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10215                 else ExitEvent(0);
10216             } else DisplayFatalError(buf, 0, 0);
10217         } else { // match through menu; just stop, with or without popup
10218             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10219             if(ranking){
10220                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10221             } else DisplayNote(buf);
10222       }
10223       if(ranking) free(ranking);
10224     }
10225 }
10226
10227 /* Assumes program was just initialized (initString sent).
10228    Leaves program in force mode. */
10229 void
10230 FeedMovesToProgram(cps, upto)
10231      ChessProgramState *cps;
10232      int upto;
10233 {
10234     int i;
10235
10236     if (appData.debugMode)
10237       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10238               startedFromSetupPosition ? "position and " : "",
10239               backwardMostMove, upto, cps->which);
10240     if(currentlyInitializedVariant != gameInfo.variant) {
10241       char buf[MSG_SIZ];
10242         // [HGM] variantswitch: make engine aware of new variant
10243         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10244                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10245         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10246         SendToProgram(buf, cps);
10247         currentlyInitializedVariant = gameInfo.variant;
10248     }
10249     SendToProgram("force\n", cps);
10250     if (startedFromSetupPosition) {
10251         SendBoard(cps, backwardMostMove);
10252     if (appData.debugMode) {
10253         fprintf(debugFP, "feedMoves\n");
10254     }
10255     }
10256     for (i = backwardMostMove; i < upto; i++) {
10257         SendMoveToProgram(i, cps);
10258     }
10259 }
10260
10261
10262 int
10263 ResurrectChessProgram()
10264 {
10265      /* The chess program may have exited.
10266         If so, restart it and feed it all the moves made so far. */
10267     static int doInit = 0;
10268
10269     if (appData.noChessProgram) return 1;
10270
10271     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10272         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10273         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10274         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10275     } else {
10276         if (first.pr != NoProc) return 1;
10277         StartChessProgram(&first);
10278     }
10279     InitChessProgram(&first, FALSE);
10280     FeedMovesToProgram(&first, currentMove);
10281
10282     if (!first.sendTime) {
10283         /* can't tell gnuchess what its clock should read,
10284            so we bow to its notion. */
10285         ResetClocks();
10286         timeRemaining[0][currentMove] = whiteTimeRemaining;
10287         timeRemaining[1][currentMove] = blackTimeRemaining;
10288     }
10289
10290     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10291                 appData.icsEngineAnalyze) && first.analysisSupport) {
10292       SendToProgram("analyze\n", &first);
10293       first.analyzing = TRUE;
10294     }
10295     return 1;
10296 }
10297
10298 /*
10299  * Button procedures
10300  */
10301 void
10302 Reset(redraw, init)
10303      int redraw, init;
10304 {
10305     int i;
10306
10307     if (appData.debugMode) {
10308         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10309                 redraw, init, gameMode);
10310     }
10311     CleanupTail(); // [HGM] vari: delete any stored variations
10312     pausing = pauseExamInvalid = FALSE;
10313     startedFromSetupPosition = blackPlaysFirst = FALSE;
10314     firstMove = TRUE;
10315     whiteFlag = blackFlag = FALSE;
10316     userOfferedDraw = FALSE;
10317     hintRequested = bookRequested = FALSE;
10318     first.maybeThinking = FALSE;
10319     second.maybeThinking = FALSE;
10320     first.bookSuspend = FALSE; // [HGM] book
10321     second.bookSuspend = FALSE;
10322     thinkOutput[0] = NULLCHAR;
10323     lastHint[0] = NULLCHAR;
10324     ClearGameInfo(&gameInfo);
10325     gameInfo.variant = StringToVariant(appData.variant);
10326     ics_user_moved = ics_clock_paused = FALSE;
10327     ics_getting_history = H_FALSE;
10328     ics_gamenum = -1;
10329     white_holding[0] = black_holding[0] = NULLCHAR;
10330     ClearProgramStats();
10331     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10332
10333     ResetFrontEnd();
10334     ClearHighlights();
10335     flipView = appData.flipView;
10336     ClearPremoveHighlights();
10337     gotPremove = FALSE;
10338     alarmSounded = FALSE;
10339
10340     GameEnds(EndOfFile, NULL, GE_PLAYER);
10341     if(appData.serverMovesName != NULL) {
10342         /* [HGM] prepare to make moves file for broadcasting */
10343         clock_t t = clock();
10344         if(serverMoves != NULL) fclose(serverMoves);
10345         serverMoves = fopen(appData.serverMovesName, "r");
10346         if(serverMoves != NULL) {
10347             fclose(serverMoves);
10348             /* delay 15 sec before overwriting, so all clients can see end */
10349             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10350         }
10351         serverMoves = fopen(appData.serverMovesName, "w");
10352     }
10353
10354     ExitAnalyzeMode();
10355     gameMode = BeginningOfGame;
10356     ModeHighlight();
10357     if(appData.icsActive) gameInfo.variant = VariantNormal;
10358     currentMove = forwardMostMove = backwardMostMove = 0;
10359     InitPosition(redraw);
10360     for (i = 0; i < MAX_MOVES; i++) {
10361         if (commentList[i] != NULL) {
10362             free(commentList[i]);
10363             commentList[i] = NULL;
10364         }
10365     }
10366     ResetClocks();
10367     timeRemaining[0][0] = whiteTimeRemaining;
10368     timeRemaining[1][0] = blackTimeRemaining;
10369
10370     if (first.pr == NULL) {
10371         StartChessProgram(&first);
10372     }
10373     if (init) {
10374             InitChessProgram(&first, startedFromSetupPosition);
10375     }
10376     DisplayTitle("");
10377     DisplayMessage("", "");
10378     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10379     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10380 }
10381
10382 void
10383 AutoPlayGameLoop()
10384 {
10385     for (;;) {
10386         if (!AutoPlayOneMove())
10387           return;
10388         if (matchMode || appData.timeDelay == 0)
10389           continue;
10390         if (appData.timeDelay < 0)
10391           return;
10392         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10393         break;
10394     }
10395 }
10396
10397
10398 int
10399 AutoPlayOneMove()
10400 {
10401     int fromX, fromY, toX, toY;
10402
10403     if (appData.debugMode) {
10404       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10405     }
10406
10407     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10408       return FALSE;
10409
10410     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10411       pvInfoList[currentMove].depth = programStats.depth;
10412       pvInfoList[currentMove].score = programStats.score;
10413       pvInfoList[currentMove].time  = 0;
10414       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10415     }
10416
10417     if (currentMove >= forwardMostMove) {
10418       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10419       gameMode = EditGame;
10420       ModeHighlight();
10421
10422       /* [AS] Clear current move marker at the end of a game */
10423       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10424
10425       return FALSE;
10426     }
10427
10428     toX = moveList[currentMove][2] - AAA;
10429     toY = moveList[currentMove][3] - ONE;
10430
10431     if (moveList[currentMove][1] == '@') {
10432         if (appData.highlightLastMove) {
10433             SetHighlights(-1, -1, toX, toY);
10434         }
10435     } else {
10436         fromX = moveList[currentMove][0] - AAA;
10437         fromY = moveList[currentMove][1] - ONE;
10438
10439         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10440
10441         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10442
10443         if (appData.highlightLastMove) {
10444             SetHighlights(fromX, fromY, toX, toY);
10445         }
10446     }
10447     DisplayMove(currentMove);
10448     SendMoveToProgram(currentMove++, &first);
10449     DisplayBothClocks();
10450     DrawPosition(FALSE, boards[currentMove]);
10451     // [HGM] PV info: always display, routine tests if empty
10452     DisplayComment(currentMove - 1, commentList[currentMove]);
10453     return TRUE;
10454 }
10455
10456
10457 int
10458 LoadGameOneMove(readAhead)
10459      ChessMove readAhead;
10460 {
10461     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10462     char promoChar = NULLCHAR;
10463     ChessMove moveType;
10464     char move[MSG_SIZ];
10465     char *p, *q;
10466
10467     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10468         gameMode != AnalyzeMode && gameMode != Training) {
10469         gameFileFP = NULL;
10470         return FALSE;
10471     }
10472
10473     yyboardindex = forwardMostMove;
10474     if (readAhead != EndOfFile) {
10475       moveType = readAhead;
10476     } else {
10477       if (gameFileFP == NULL)
10478           return FALSE;
10479       moveType = (ChessMove) Myylex();
10480     }
10481
10482     done = FALSE;
10483     switch (moveType) {
10484       case Comment:
10485         if (appData.debugMode)
10486           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10487         p = yy_text;
10488
10489         /* append the comment but don't display it */
10490         AppendComment(currentMove, p, FALSE);
10491         return TRUE;
10492
10493       case WhiteCapturesEnPassant:
10494       case BlackCapturesEnPassant:
10495       case WhitePromotion:
10496       case BlackPromotion:
10497       case WhiteNonPromotion:
10498       case BlackNonPromotion:
10499       case NormalMove:
10500       case WhiteKingSideCastle:
10501       case WhiteQueenSideCastle:
10502       case BlackKingSideCastle:
10503       case BlackQueenSideCastle:
10504       case WhiteKingSideCastleWild:
10505       case WhiteQueenSideCastleWild:
10506       case BlackKingSideCastleWild:
10507       case BlackQueenSideCastleWild:
10508       /* PUSH Fabien */
10509       case WhiteHSideCastleFR:
10510       case WhiteASideCastleFR:
10511       case BlackHSideCastleFR:
10512       case BlackASideCastleFR:
10513       /* POP Fabien */
10514         if (appData.debugMode)
10515           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10516         fromX = currentMoveString[0] - AAA;
10517         fromY = currentMoveString[1] - ONE;
10518         toX = currentMoveString[2] - AAA;
10519         toY = currentMoveString[3] - ONE;
10520         promoChar = currentMoveString[4];
10521         break;
10522
10523       case WhiteDrop:
10524       case BlackDrop:
10525         if (appData.debugMode)
10526           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10527         fromX = moveType == WhiteDrop ?
10528           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10529         (int) CharToPiece(ToLower(currentMoveString[0]));
10530         fromY = DROP_RANK;
10531         toX = currentMoveString[2] - AAA;
10532         toY = currentMoveString[3] - ONE;
10533         break;
10534
10535       case WhiteWins:
10536       case BlackWins:
10537       case GameIsDrawn:
10538       case GameUnfinished:
10539         if (appData.debugMode)
10540           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10541         p = strchr(yy_text, '{');
10542         if (p == NULL) p = strchr(yy_text, '(');
10543         if (p == NULL) {
10544             p = yy_text;
10545             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10546         } else {
10547             q = strchr(p, *p == '{' ? '}' : ')');
10548             if (q != NULL) *q = NULLCHAR;
10549             p++;
10550         }
10551         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10552         GameEnds(moveType, p, GE_FILE);
10553         done = TRUE;
10554         if (cmailMsgLoaded) {
10555             ClearHighlights();
10556             flipView = WhiteOnMove(currentMove);
10557             if (moveType == GameUnfinished) flipView = !flipView;
10558             if (appData.debugMode)
10559               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10560         }
10561         break;
10562
10563       case EndOfFile:
10564         if (appData.debugMode)
10565           fprintf(debugFP, "Parser hit end of file\n");
10566         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10567           case MT_NONE:
10568           case MT_CHECK:
10569             break;
10570           case MT_CHECKMATE:
10571           case MT_STAINMATE:
10572             if (WhiteOnMove(currentMove)) {
10573                 GameEnds(BlackWins, "Black mates", GE_FILE);
10574             } else {
10575                 GameEnds(WhiteWins, "White mates", GE_FILE);
10576             }
10577             break;
10578           case MT_STALEMATE:
10579             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10580             break;
10581         }
10582         done = TRUE;
10583         break;
10584
10585       case MoveNumberOne:
10586         if (lastLoadGameStart == GNUChessGame) {
10587             /* GNUChessGames have numbers, but they aren't move numbers */
10588             if (appData.debugMode)
10589               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10590                       yy_text, (int) moveType);
10591             return LoadGameOneMove(EndOfFile); /* tail recursion */
10592         }
10593         /* else fall thru */
10594
10595       case XBoardGame:
10596       case GNUChessGame:
10597       case PGNTag:
10598         /* Reached start of next game in file */
10599         if (appData.debugMode)
10600           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10601         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10602           case MT_NONE:
10603           case MT_CHECK:
10604             break;
10605           case MT_CHECKMATE:
10606           case MT_STAINMATE:
10607             if (WhiteOnMove(currentMove)) {
10608                 GameEnds(BlackWins, "Black mates", GE_FILE);
10609             } else {
10610                 GameEnds(WhiteWins, "White mates", GE_FILE);
10611             }
10612             break;
10613           case MT_STALEMATE:
10614             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10615             break;
10616         }
10617         done = TRUE;
10618         break;
10619
10620       case PositionDiagram:     /* should not happen; ignore */
10621       case ElapsedTime:         /* ignore */
10622       case NAG:                 /* ignore */
10623         if (appData.debugMode)
10624           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10625                   yy_text, (int) moveType);
10626         return LoadGameOneMove(EndOfFile); /* tail recursion */
10627
10628       case IllegalMove:
10629         if (appData.testLegality) {
10630             if (appData.debugMode)
10631               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10632             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10633                     (forwardMostMove / 2) + 1,
10634                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10635             DisplayError(move, 0);
10636             done = TRUE;
10637         } else {
10638             if (appData.debugMode)
10639               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10640                       yy_text, currentMoveString);
10641             fromX = currentMoveString[0] - AAA;
10642             fromY = currentMoveString[1] - ONE;
10643             toX = currentMoveString[2] - AAA;
10644             toY = currentMoveString[3] - ONE;
10645             promoChar = currentMoveString[4];
10646         }
10647         break;
10648
10649       case AmbiguousMove:
10650         if (appData.debugMode)
10651           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10652         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10653                 (forwardMostMove / 2) + 1,
10654                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10655         DisplayError(move, 0);
10656         done = TRUE;
10657         break;
10658
10659       default:
10660       case ImpossibleMove:
10661         if (appData.debugMode)
10662           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10663         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10664                 (forwardMostMove / 2) + 1,
10665                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10666         DisplayError(move, 0);
10667         done = TRUE;
10668         break;
10669     }
10670
10671     if (done) {
10672         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10673             DrawPosition(FALSE, boards[currentMove]);
10674             DisplayBothClocks();
10675             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10676               DisplayComment(currentMove - 1, commentList[currentMove]);
10677         }
10678         (void) StopLoadGameTimer();
10679         gameFileFP = NULL;
10680         cmailOldMove = forwardMostMove;
10681         return FALSE;
10682     } else {
10683         /* currentMoveString is set as a side-effect of yylex */
10684
10685         thinkOutput[0] = NULLCHAR;
10686         MakeMove(fromX, fromY, toX, toY, promoChar);
10687         currentMove = forwardMostMove;
10688         return TRUE;
10689     }
10690 }
10691
10692 /* Load the nth game from the given file */
10693 int
10694 LoadGameFromFile(filename, n, title, useList)
10695      char *filename;
10696      int n;
10697      char *title;
10698      /*Boolean*/ int useList;
10699 {
10700     FILE *f;
10701     char buf[MSG_SIZ];
10702
10703     if (strcmp(filename, "-") == 0) {
10704         f = stdin;
10705         title = "stdin";
10706     } else {
10707         f = fopen(filename, "rb");
10708         if (f == NULL) {
10709           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10710             DisplayError(buf, errno);
10711             return FALSE;
10712         }
10713     }
10714     if (fseek(f, 0, 0) == -1) {
10715         /* f is not seekable; probably a pipe */
10716         useList = FALSE;
10717     }
10718     if (useList && n == 0) {
10719         int error = GameListBuild(f);
10720         if (error) {
10721             DisplayError(_("Cannot build game list"), error);
10722         } else if (!ListEmpty(&gameList) &&
10723                    ((ListGame *) gameList.tailPred)->number > 1) {
10724             GameListPopUp(f, title);
10725             return TRUE;
10726         }
10727         GameListDestroy();
10728         n = 1;
10729     }
10730     if (n == 0) n = 1;
10731     return LoadGame(f, n, title, FALSE);
10732 }
10733
10734
10735 void
10736 MakeRegisteredMove()
10737 {
10738     int fromX, fromY, toX, toY;
10739     char promoChar;
10740     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10741         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10742           case CMAIL_MOVE:
10743           case CMAIL_DRAW:
10744             if (appData.debugMode)
10745               fprintf(debugFP, "Restoring %s for game %d\n",
10746                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10747
10748             thinkOutput[0] = NULLCHAR;
10749             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10750             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10751             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10752             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10753             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10754             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10755             MakeMove(fromX, fromY, toX, toY, promoChar);
10756             ShowMove(fromX, fromY, toX, toY);
10757
10758             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10759               case MT_NONE:
10760               case MT_CHECK:
10761                 break;
10762
10763               case MT_CHECKMATE:
10764               case MT_STAINMATE:
10765                 if (WhiteOnMove(currentMove)) {
10766                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10767                 } else {
10768                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10769                 }
10770                 break;
10771
10772               case MT_STALEMATE:
10773                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10774                 break;
10775             }
10776
10777             break;
10778
10779           case CMAIL_RESIGN:
10780             if (WhiteOnMove(currentMove)) {
10781                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10782             } else {
10783                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10784             }
10785             break;
10786
10787           case CMAIL_ACCEPT:
10788             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10789             break;
10790
10791           default:
10792             break;
10793         }
10794     }
10795
10796     return;
10797 }
10798
10799 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10800 int
10801 CmailLoadGame(f, gameNumber, title, useList)
10802      FILE *f;
10803      int gameNumber;
10804      char *title;
10805      int useList;
10806 {
10807     int retVal;
10808
10809     if (gameNumber > nCmailGames) {
10810         DisplayError(_("No more games in this message"), 0);
10811         return FALSE;
10812     }
10813     if (f == lastLoadGameFP) {
10814         int offset = gameNumber - lastLoadGameNumber;
10815         if (offset == 0) {
10816             cmailMsg[0] = NULLCHAR;
10817             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10818                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10819                 nCmailMovesRegistered--;
10820             }
10821             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10822             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10823                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10824             }
10825         } else {
10826             if (! RegisterMove()) return FALSE;
10827         }
10828     }
10829
10830     retVal = LoadGame(f, gameNumber, title, useList);
10831
10832     /* Make move registered during previous look at this game, if any */
10833     MakeRegisteredMove();
10834
10835     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10836         commentList[currentMove]
10837           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10838         DisplayComment(currentMove - 1, commentList[currentMove]);
10839     }
10840
10841     return retVal;
10842 }
10843
10844 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10845 int
10846 ReloadGame(offset)
10847      int offset;
10848 {
10849     int gameNumber = lastLoadGameNumber + offset;
10850     if (lastLoadGameFP == NULL) {
10851         DisplayError(_("No game has been loaded yet"), 0);
10852         return FALSE;
10853     }
10854     if (gameNumber <= 0) {
10855         DisplayError(_("Can't back up any further"), 0);
10856         return FALSE;
10857     }
10858     if (cmailMsgLoaded) {
10859         return CmailLoadGame(lastLoadGameFP, gameNumber,
10860                              lastLoadGameTitle, lastLoadGameUseList);
10861     } else {
10862         return LoadGame(lastLoadGameFP, gameNumber,
10863                         lastLoadGameTitle, lastLoadGameUseList);
10864     }
10865 }
10866
10867
10868
10869 /* Load the nth game from open file f */
10870 int
10871 LoadGame(f, gameNumber, title, useList)
10872      FILE *f;
10873      int gameNumber;
10874      char *title;
10875      int useList;
10876 {
10877     ChessMove cm;
10878     char buf[MSG_SIZ];
10879     int gn = gameNumber;
10880     ListGame *lg = NULL;
10881     int numPGNTags = 0;
10882     int err;
10883     GameMode oldGameMode;
10884     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10885
10886     if (appData.debugMode)
10887         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10888
10889     if (gameMode == Training )
10890         SetTrainingModeOff();
10891
10892     oldGameMode = gameMode;
10893     if (gameMode != BeginningOfGame) {
10894       Reset(FALSE, TRUE);
10895     }
10896
10897     gameFileFP = f;
10898     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10899         fclose(lastLoadGameFP);
10900     }
10901
10902     if (useList) {
10903         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10904
10905         if (lg) {
10906             fseek(f, lg->offset, 0);
10907             GameListHighlight(gameNumber);
10908             gn = 1;
10909         }
10910         else {
10911             DisplayError(_("Game number out of range"), 0);
10912             return FALSE;
10913         }
10914     } else {
10915         GameListDestroy();
10916         if (fseek(f, 0, 0) == -1) {
10917             if (f == lastLoadGameFP ?
10918                 gameNumber == lastLoadGameNumber + 1 :
10919                 gameNumber == 1) {
10920                 gn = 1;
10921             } else {
10922                 DisplayError(_("Can't seek on game file"), 0);
10923                 return FALSE;
10924             }
10925         }
10926     }
10927     lastLoadGameFP = f;
10928     lastLoadGameNumber = gameNumber;
10929     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10930     lastLoadGameUseList = useList;
10931
10932     yynewfile(f);
10933
10934     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10935       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10936                 lg->gameInfo.black);
10937             DisplayTitle(buf);
10938     } else if (*title != NULLCHAR) {
10939         if (gameNumber > 1) {
10940           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10941             DisplayTitle(buf);
10942         } else {
10943             DisplayTitle(title);
10944         }
10945     }
10946
10947     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10948         gameMode = PlayFromGameFile;
10949         ModeHighlight();
10950     }
10951
10952     currentMove = forwardMostMove = backwardMostMove = 0;
10953     CopyBoard(boards[0], initialPosition);
10954     StopClocks();
10955
10956     /*
10957      * Skip the first gn-1 games in the file.
10958      * Also skip over anything that precedes an identifiable
10959      * start of game marker, to avoid being confused by
10960      * garbage at the start of the file.  Currently
10961      * recognized start of game markers are the move number "1",
10962      * the pattern "gnuchess .* game", the pattern
10963      * "^[#;%] [^ ]* game file", and a PGN tag block.
10964      * A game that starts with one of the latter two patterns
10965      * will also have a move number 1, possibly
10966      * following a position diagram.
10967      * 5-4-02: Let's try being more lenient and allowing a game to
10968      * start with an unnumbered move.  Does that break anything?
10969      */
10970     cm = lastLoadGameStart = EndOfFile;
10971     while (gn > 0) {
10972         yyboardindex = forwardMostMove;
10973         cm = (ChessMove) Myylex();
10974         switch (cm) {
10975           case EndOfFile:
10976             if (cmailMsgLoaded) {
10977                 nCmailGames = CMAIL_MAX_GAMES - gn;
10978             } else {
10979                 Reset(TRUE, TRUE);
10980                 DisplayError(_("Game not found in file"), 0);
10981             }
10982             return FALSE;
10983
10984           case GNUChessGame:
10985           case XBoardGame:
10986             gn--;
10987             lastLoadGameStart = cm;
10988             break;
10989
10990           case MoveNumberOne:
10991             switch (lastLoadGameStart) {
10992               case GNUChessGame:
10993               case XBoardGame:
10994               case PGNTag:
10995                 break;
10996               case MoveNumberOne:
10997               case EndOfFile:
10998                 gn--;           /* count this game */
10999                 lastLoadGameStart = cm;
11000                 break;
11001               default:
11002                 /* impossible */
11003                 break;
11004             }
11005             break;
11006
11007           case PGNTag:
11008             switch (lastLoadGameStart) {
11009               case GNUChessGame:
11010               case PGNTag:
11011               case MoveNumberOne:
11012               case EndOfFile:
11013                 gn--;           /* count this game */
11014                 lastLoadGameStart = cm;
11015                 break;
11016               case XBoardGame:
11017                 lastLoadGameStart = cm; /* game counted already */
11018                 break;
11019               default:
11020                 /* impossible */
11021                 break;
11022             }
11023             if (gn > 0) {
11024                 do {
11025                     yyboardindex = forwardMostMove;
11026                     cm = (ChessMove) Myylex();
11027                 } while (cm == PGNTag || cm == Comment);
11028             }
11029             break;
11030
11031           case WhiteWins:
11032           case BlackWins:
11033           case GameIsDrawn:
11034             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11035                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11036                     != CMAIL_OLD_RESULT) {
11037                     nCmailResults ++ ;
11038                     cmailResult[  CMAIL_MAX_GAMES
11039                                 - gn - 1] = CMAIL_OLD_RESULT;
11040                 }
11041             }
11042             break;
11043
11044           case NormalMove:
11045             /* Only a NormalMove can be at the start of a game
11046              * without a position diagram. */
11047             if (lastLoadGameStart == EndOfFile ) {
11048               gn--;
11049               lastLoadGameStart = MoveNumberOne;
11050             }
11051             break;
11052
11053           default:
11054             break;
11055         }
11056     }
11057
11058     if (appData.debugMode)
11059       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11060
11061     if (cm == XBoardGame) {
11062         /* Skip any header junk before position diagram and/or move 1 */
11063         for (;;) {
11064             yyboardindex = forwardMostMove;
11065             cm = (ChessMove) Myylex();
11066
11067             if (cm == EndOfFile ||
11068                 cm == GNUChessGame || cm == XBoardGame) {
11069                 /* Empty game; pretend end-of-file and handle later */
11070                 cm = EndOfFile;
11071                 break;
11072             }
11073
11074             if (cm == MoveNumberOne || cm == PositionDiagram ||
11075                 cm == PGNTag || cm == Comment)
11076               break;
11077         }
11078     } else if (cm == GNUChessGame) {
11079         if (gameInfo.event != NULL) {
11080             free(gameInfo.event);
11081         }
11082         gameInfo.event = StrSave(yy_text);
11083     }
11084
11085     startedFromSetupPosition = FALSE;
11086     while (cm == PGNTag) {
11087         if (appData.debugMode)
11088           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11089         err = ParsePGNTag(yy_text, &gameInfo);
11090         if (!err) numPGNTags++;
11091
11092         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11093         if(gameInfo.variant != oldVariant) {
11094             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11095             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11096             InitPosition(TRUE);
11097             oldVariant = gameInfo.variant;
11098             if (appData.debugMode)
11099               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11100         }
11101
11102
11103         if (gameInfo.fen != NULL) {
11104           Board initial_position;
11105           startedFromSetupPosition = TRUE;
11106           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11107             Reset(TRUE, TRUE);
11108             DisplayError(_("Bad FEN position in file"), 0);
11109             return FALSE;
11110           }
11111           CopyBoard(boards[0], initial_position);
11112           if (blackPlaysFirst) {
11113             currentMove = forwardMostMove = backwardMostMove = 1;
11114             CopyBoard(boards[1], initial_position);
11115             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11116             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11117             timeRemaining[0][1] = whiteTimeRemaining;
11118             timeRemaining[1][1] = blackTimeRemaining;
11119             if (commentList[0] != NULL) {
11120               commentList[1] = commentList[0];
11121               commentList[0] = NULL;
11122             }
11123           } else {
11124             currentMove = forwardMostMove = backwardMostMove = 0;
11125           }
11126           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11127           {   int i;
11128               initialRulePlies = FENrulePlies;
11129               for( i=0; i< nrCastlingRights; i++ )
11130                   initialRights[i] = initial_position[CASTLING][i];
11131           }
11132           yyboardindex = forwardMostMove;
11133           free(gameInfo.fen);
11134           gameInfo.fen = NULL;
11135         }
11136
11137         yyboardindex = forwardMostMove;
11138         cm = (ChessMove) Myylex();
11139
11140         /* Handle comments interspersed among the tags */
11141         while (cm == Comment) {
11142             char *p;
11143             if (appData.debugMode)
11144               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11145             p = yy_text;
11146             AppendComment(currentMove, p, FALSE);
11147             yyboardindex = forwardMostMove;
11148             cm = (ChessMove) Myylex();
11149         }
11150     }
11151
11152     /* don't rely on existence of Event tag since if game was
11153      * pasted from clipboard the Event tag may not exist
11154      */
11155     if (numPGNTags > 0){
11156         char *tags;
11157         if (gameInfo.variant == VariantNormal) {
11158           VariantClass v = StringToVariant(gameInfo.event);
11159           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11160           if(v < VariantShogi) gameInfo.variant = v;
11161         }
11162         if (!matchMode) {
11163           if( appData.autoDisplayTags ) {
11164             tags = PGNTags(&gameInfo);
11165             TagsPopUp(tags, CmailMsg());
11166             free(tags);
11167           }
11168         }
11169     } else {
11170         /* Make something up, but don't display it now */
11171         SetGameInfo();
11172         TagsPopDown();
11173     }
11174
11175     if (cm == PositionDiagram) {
11176         int i, j;
11177         char *p;
11178         Board initial_position;
11179
11180         if (appData.debugMode)
11181           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11182
11183         if (!startedFromSetupPosition) {
11184             p = yy_text;
11185             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11186               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11187                 switch (*p) {
11188                   case '{':
11189                   case '[':
11190                   case '-':
11191                   case ' ':
11192                   case '\t':
11193                   case '\n':
11194                   case '\r':
11195                     break;
11196                   default:
11197                     initial_position[i][j++] = CharToPiece(*p);
11198                     break;
11199                 }
11200             while (*p == ' ' || *p == '\t' ||
11201                    *p == '\n' || *p == '\r') p++;
11202
11203             if (strncmp(p, "black", strlen("black"))==0)
11204               blackPlaysFirst = TRUE;
11205             else
11206               blackPlaysFirst = FALSE;
11207             startedFromSetupPosition = TRUE;
11208
11209             CopyBoard(boards[0], initial_position);
11210             if (blackPlaysFirst) {
11211                 currentMove = forwardMostMove = backwardMostMove = 1;
11212                 CopyBoard(boards[1], initial_position);
11213                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11214                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11215                 timeRemaining[0][1] = whiteTimeRemaining;
11216                 timeRemaining[1][1] = blackTimeRemaining;
11217                 if (commentList[0] != NULL) {
11218                     commentList[1] = commentList[0];
11219                     commentList[0] = NULL;
11220                 }
11221             } else {
11222                 currentMove = forwardMostMove = backwardMostMove = 0;
11223             }
11224         }
11225         yyboardindex = forwardMostMove;
11226         cm = (ChessMove) Myylex();
11227     }
11228
11229     if (first.pr == NoProc) {
11230         StartChessProgram(&first);
11231     }
11232     InitChessProgram(&first, FALSE);
11233     SendToProgram("force\n", &first);
11234     if (startedFromSetupPosition) {
11235         SendBoard(&first, forwardMostMove);
11236     if (appData.debugMode) {
11237         fprintf(debugFP, "Load Game\n");
11238     }
11239         DisplayBothClocks();
11240     }
11241
11242     /* [HGM] server: flag to write setup moves in broadcast file as one */
11243     loadFlag = appData.suppressLoadMoves;
11244
11245     while (cm == Comment) {
11246         char *p;
11247         if (appData.debugMode)
11248           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11249         p = yy_text;
11250         AppendComment(currentMove, p, FALSE);
11251         yyboardindex = forwardMostMove;
11252         cm = (ChessMove) Myylex();
11253     }
11254
11255     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11256         cm == WhiteWins || cm == BlackWins ||
11257         cm == GameIsDrawn || cm == GameUnfinished) {
11258         DisplayMessage("", _("No moves in game"));
11259         if (cmailMsgLoaded) {
11260             if (appData.debugMode)
11261               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11262             ClearHighlights();
11263             flipView = FALSE;
11264         }
11265         DrawPosition(FALSE, boards[currentMove]);
11266         DisplayBothClocks();
11267         gameMode = EditGame;
11268         ModeHighlight();
11269         gameFileFP = NULL;
11270         cmailOldMove = 0;
11271         return TRUE;
11272     }
11273
11274     // [HGM] PV info: routine tests if comment empty
11275     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11276         DisplayComment(currentMove - 1, commentList[currentMove]);
11277     }
11278     if (!matchMode && appData.timeDelay != 0)
11279       DrawPosition(FALSE, boards[currentMove]);
11280
11281     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11282       programStats.ok_to_send = 1;
11283     }
11284
11285     /* if the first token after the PGN tags is a move
11286      * and not move number 1, retrieve it from the parser
11287      */
11288     if (cm != MoveNumberOne)
11289         LoadGameOneMove(cm);
11290
11291     /* load the remaining moves from the file */
11292     while (LoadGameOneMove(EndOfFile)) {
11293       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11294       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11295     }
11296
11297     /* rewind to the start of the game */
11298     currentMove = backwardMostMove;
11299
11300     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11301
11302     if (oldGameMode == AnalyzeFile ||
11303         oldGameMode == AnalyzeMode) {
11304       AnalyzeFileEvent();
11305     }
11306
11307     if (matchMode || appData.timeDelay == 0) {
11308       ToEndEvent();
11309       gameMode = EditGame;
11310       ModeHighlight();
11311     } else if (appData.timeDelay > 0) {
11312       AutoPlayGameLoop();
11313     }
11314
11315     if (appData.debugMode)
11316         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11317
11318     loadFlag = 0; /* [HGM] true game starts */
11319     return TRUE;
11320 }
11321
11322 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11323 int
11324 ReloadPosition(offset)
11325      int offset;
11326 {
11327     int positionNumber = lastLoadPositionNumber + offset;
11328     if (lastLoadPositionFP == NULL) {
11329         DisplayError(_("No position has been loaded yet"), 0);
11330         return FALSE;
11331     }
11332     if (positionNumber <= 0) {
11333         DisplayError(_("Can't back up any further"), 0);
11334         return FALSE;
11335     }
11336     return LoadPosition(lastLoadPositionFP, positionNumber,
11337                         lastLoadPositionTitle);
11338 }
11339
11340 /* Load the nth position from the given file */
11341 int
11342 LoadPositionFromFile(filename, n, title)
11343      char *filename;
11344      int n;
11345      char *title;
11346 {
11347     FILE *f;
11348     char buf[MSG_SIZ];
11349
11350     if (strcmp(filename, "-") == 0) {
11351         return LoadPosition(stdin, n, "stdin");
11352     } else {
11353         f = fopen(filename, "rb");
11354         if (f == NULL) {
11355             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11356             DisplayError(buf, errno);
11357             return FALSE;
11358         } else {
11359             return LoadPosition(f, n, title);
11360         }
11361     }
11362 }
11363
11364 /* Load the nth position from the given open file, and close it */
11365 int
11366 LoadPosition(f, positionNumber, title)
11367      FILE *f;
11368      int positionNumber;
11369      char *title;
11370 {
11371     char *p, line[MSG_SIZ];
11372     Board initial_position;
11373     int i, j, fenMode, pn;
11374
11375     if (gameMode == Training )
11376         SetTrainingModeOff();
11377
11378     if (gameMode != BeginningOfGame) {
11379         Reset(FALSE, TRUE);
11380     }
11381     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11382         fclose(lastLoadPositionFP);
11383     }
11384     if (positionNumber == 0) positionNumber = 1;
11385     lastLoadPositionFP = f;
11386     lastLoadPositionNumber = positionNumber;
11387     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11388     if (first.pr == NoProc) {
11389       StartChessProgram(&first);
11390       InitChessProgram(&first, FALSE);
11391     }
11392     pn = positionNumber;
11393     if (positionNumber < 0) {
11394         /* Negative position number means to seek to that byte offset */
11395         if (fseek(f, -positionNumber, 0) == -1) {
11396             DisplayError(_("Can't seek on position file"), 0);
11397             return FALSE;
11398         };
11399         pn = 1;
11400     } else {
11401         if (fseek(f, 0, 0) == -1) {
11402             if (f == lastLoadPositionFP ?
11403                 positionNumber == lastLoadPositionNumber + 1 :
11404                 positionNumber == 1) {
11405                 pn = 1;
11406             } else {
11407                 DisplayError(_("Can't seek on position file"), 0);
11408                 return FALSE;
11409             }
11410         }
11411     }
11412     /* See if this file is FEN or old-style xboard */
11413     if (fgets(line, MSG_SIZ, f) == NULL) {
11414         DisplayError(_("Position not found in file"), 0);
11415         return FALSE;
11416     }
11417     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11418     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11419
11420     if (pn >= 2) {
11421         if (fenMode || line[0] == '#') pn--;
11422         while (pn > 0) {
11423             /* skip positions before number pn */
11424             if (fgets(line, MSG_SIZ, f) == NULL) {
11425                 Reset(TRUE, TRUE);
11426                 DisplayError(_("Position not found in file"), 0);
11427                 return FALSE;
11428             }
11429             if (fenMode || line[0] == '#') pn--;
11430         }
11431     }
11432
11433     if (fenMode) {
11434         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11435             DisplayError(_("Bad FEN position in file"), 0);
11436             return FALSE;
11437         }
11438     } else {
11439         (void) fgets(line, MSG_SIZ, f);
11440         (void) fgets(line, MSG_SIZ, f);
11441
11442         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11443             (void) fgets(line, MSG_SIZ, f);
11444             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11445                 if (*p == ' ')
11446                   continue;
11447                 initial_position[i][j++] = CharToPiece(*p);
11448             }
11449         }
11450
11451         blackPlaysFirst = FALSE;
11452         if (!feof(f)) {
11453             (void) fgets(line, MSG_SIZ, f);
11454             if (strncmp(line, "black", strlen("black"))==0)
11455               blackPlaysFirst = TRUE;
11456         }
11457     }
11458     startedFromSetupPosition = TRUE;
11459
11460     SendToProgram("force\n", &first);
11461     CopyBoard(boards[0], initial_position);
11462     if (blackPlaysFirst) {
11463         currentMove = forwardMostMove = backwardMostMove = 1;
11464         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11465         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11466         CopyBoard(boards[1], initial_position);
11467         DisplayMessage("", _("Black to play"));
11468     } else {
11469         currentMove = forwardMostMove = backwardMostMove = 0;
11470         DisplayMessage("", _("White to play"));
11471     }
11472     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11473     SendBoard(&first, forwardMostMove);
11474     if (appData.debugMode) {
11475 int i, j;
11476   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11477   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11478         fprintf(debugFP, "Load Position\n");
11479     }
11480
11481     if (positionNumber > 1) {
11482       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11483         DisplayTitle(line);
11484     } else {
11485         DisplayTitle(title);
11486     }
11487     gameMode = EditGame;
11488     ModeHighlight();
11489     ResetClocks();
11490     timeRemaining[0][1] = whiteTimeRemaining;
11491     timeRemaining[1][1] = blackTimeRemaining;
11492     DrawPosition(FALSE, boards[currentMove]);
11493
11494     return TRUE;
11495 }
11496
11497
11498 void
11499 CopyPlayerNameIntoFileName(dest, src)
11500      char **dest, *src;
11501 {
11502     while (*src != NULLCHAR && *src != ',') {
11503         if (*src == ' ') {
11504             *(*dest)++ = '_';
11505             src++;
11506         } else {
11507             *(*dest)++ = *src++;
11508         }
11509     }
11510 }
11511
11512 char *DefaultFileName(ext)
11513      char *ext;
11514 {
11515     static char def[MSG_SIZ];
11516     char *p;
11517
11518     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11519         p = def;
11520         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11521         *p++ = '-';
11522         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11523         *p++ = '.';
11524         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11525     } else {
11526         def[0] = NULLCHAR;
11527     }
11528     return def;
11529 }
11530
11531 /* Save the current game to the given file */
11532 int
11533 SaveGameToFile(filename, append)
11534      char *filename;
11535      int append;
11536 {
11537     FILE *f;
11538     char buf[MSG_SIZ];
11539     int result;
11540
11541     if (strcmp(filename, "-") == 0) {
11542         return SaveGame(stdout, 0, NULL);
11543     } else {
11544         f = fopen(filename, append ? "a" : "w");
11545         if (f == NULL) {
11546             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11547             DisplayError(buf, errno);
11548             return FALSE;
11549         } else {
11550             safeStrCpy(buf, lastMsg, MSG_SIZ);
11551             DisplayMessage(_("Waiting for access to save file"), "");
11552             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11553             DisplayMessage(_("Saving game"), "");
11554             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11555             result = SaveGame(f, 0, NULL);
11556             DisplayMessage(buf, "");
11557             return result;
11558         }
11559     }
11560 }
11561
11562 char *
11563 SavePart(str)
11564      char *str;
11565 {
11566     static char buf[MSG_SIZ];
11567     char *p;
11568
11569     p = strchr(str, ' ');
11570     if (p == NULL) return str;
11571     strncpy(buf, str, p - str);
11572     buf[p - str] = NULLCHAR;
11573     return buf;
11574 }
11575
11576 #define PGN_MAX_LINE 75
11577
11578 #define PGN_SIDE_WHITE  0
11579 #define PGN_SIDE_BLACK  1
11580
11581 /* [AS] */
11582 static int FindFirstMoveOutOfBook( int side )
11583 {
11584     int result = -1;
11585
11586     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11587         int index = backwardMostMove;
11588         int has_book_hit = 0;
11589
11590         if( (index % 2) != side ) {
11591             index++;
11592         }
11593
11594         while( index < forwardMostMove ) {
11595             /* Check to see if engine is in book */
11596             int depth = pvInfoList[index].depth;
11597             int score = pvInfoList[index].score;
11598             int in_book = 0;
11599
11600             if( depth <= 2 ) {
11601                 in_book = 1;
11602             }
11603             else if( score == 0 && depth == 63 ) {
11604                 in_book = 1; /* Zappa */
11605             }
11606             else if( score == 2 && depth == 99 ) {
11607                 in_book = 1; /* Abrok */
11608             }
11609
11610             has_book_hit += in_book;
11611
11612             if( ! in_book ) {
11613                 result = index;
11614
11615                 break;
11616             }
11617
11618             index += 2;
11619         }
11620     }
11621
11622     return result;
11623 }
11624
11625 /* [AS] */
11626 void GetOutOfBookInfo( char * buf )
11627 {
11628     int oob[2];
11629     int i;
11630     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11631
11632     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11633     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11634
11635     *buf = '\0';
11636
11637     if( oob[0] >= 0 || oob[1] >= 0 ) {
11638         for( i=0; i<2; i++ ) {
11639             int idx = oob[i];
11640
11641             if( idx >= 0 ) {
11642                 if( i > 0 && oob[0] >= 0 ) {
11643                     strcat( buf, "   " );
11644                 }
11645
11646                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11647                 sprintf( buf+strlen(buf), "%s%.2f",
11648                     pvInfoList[idx].score >= 0 ? "+" : "",
11649                     pvInfoList[idx].score / 100.0 );
11650             }
11651         }
11652     }
11653 }
11654
11655 /* Save game in PGN style and close the file */
11656 int
11657 SaveGamePGN(f)
11658      FILE *f;
11659 {
11660     int i, offset, linelen, newblock;
11661     time_t tm;
11662 //    char *movetext;
11663     char numtext[32];
11664     int movelen, numlen, blank;
11665     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11666
11667     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11668
11669     tm = time((time_t *) NULL);
11670
11671     PrintPGNTags(f, &gameInfo);
11672
11673     if (backwardMostMove > 0 || startedFromSetupPosition) {
11674         char *fen = PositionToFEN(backwardMostMove, NULL);
11675         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11676         fprintf(f, "\n{--------------\n");
11677         PrintPosition(f, backwardMostMove);
11678         fprintf(f, "--------------}\n");
11679         free(fen);
11680     }
11681     else {
11682         /* [AS] Out of book annotation */
11683         if( appData.saveOutOfBookInfo ) {
11684             char buf[64];
11685
11686             GetOutOfBookInfo( buf );
11687
11688             if( buf[0] != '\0' ) {
11689                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11690             }
11691         }
11692
11693         fprintf(f, "\n");
11694     }
11695
11696     i = backwardMostMove;
11697     linelen = 0;
11698     newblock = TRUE;
11699
11700     while (i < forwardMostMove) {
11701         /* Print comments preceding this move */
11702         if (commentList[i] != NULL) {
11703             if (linelen > 0) fprintf(f, "\n");
11704             fprintf(f, "%s", commentList[i]);
11705             linelen = 0;
11706             newblock = TRUE;
11707         }
11708
11709         /* Format move number */
11710         if ((i % 2) == 0)
11711           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11712         else
11713           if (newblock)
11714             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11715           else
11716             numtext[0] = NULLCHAR;
11717
11718         numlen = strlen(numtext);
11719         newblock = FALSE;
11720
11721         /* Print move number */
11722         blank = linelen > 0 && numlen > 0;
11723         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11724             fprintf(f, "\n");
11725             linelen = 0;
11726             blank = 0;
11727         }
11728         if (blank) {
11729             fprintf(f, " ");
11730             linelen++;
11731         }
11732         fprintf(f, "%s", numtext);
11733         linelen += numlen;
11734
11735         /* Get move */
11736         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11737         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11738
11739         /* Print move */
11740         blank = linelen > 0 && movelen > 0;
11741         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11742             fprintf(f, "\n");
11743             linelen = 0;
11744             blank = 0;
11745         }
11746         if (blank) {
11747             fprintf(f, " ");
11748             linelen++;
11749         }
11750         fprintf(f, "%s", move_buffer);
11751         linelen += movelen;
11752
11753         /* [AS] Add PV info if present */
11754         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11755             /* [HGM] add time */
11756             char buf[MSG_SIZ]; int seconds;
11757
11758             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11759
11760             if( seconds <= 0)
11761               buf[0] = 0;
11762             else
11763               if( seconds < 30 )
11764                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11765               else
11766                 {
11767                   seconds = (seconds + 4)/10; // round to full seconds
11768                   if( seconds < 60 )
11769                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11770                   else
11771                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11772                 }
11773
11774             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11775                       pvInfoList[i].score >= 0 ? "+" : "",
11776                       pvInfoList[i].score / 100.0,
11777                       pvInfoList[i].depth,
11778                       buf );
11779
11780             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11781
11782             /* Print score/depth */
11783             blank = linelen > 0 && movelen > 0;
11784             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11785                 fprintf(f, "\n");
11786                 linelen = 0;
11787                 blank = 0;
11788             }
11789             if (blank) {
11790                 fprintf(f, " ");
11791                 linelen++;
11792             }
11793             fprintf(f, "%s", move_buffer);
11794             linelen += movelen;
11795         }
11796
11797         i++;
11798     }
11799
11800     /* Start a new line */
11801     if (linelen > 0) fprintf(f, "\n");
11802
11803     /* Print comments after last move */
11804     if (commentList[i] != NULL) {
11805         fprintf(f, "%s\n", commentList[i]);
11806     }
11807
11808     /* Print result */
11809     if (gameInfo.resultDetails != NULL &&
11810         gameInfo.resultDetails[0] != NULLCHAR) {
11811         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11812                 PGNResult(gameInfo.result));
11813     } else {
11814         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11815     }
11816
11817     fclose(f);
11818     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11819     return TRUE;
11820 }
11821
11822 /* Save game in old style and close the file */
11823 int
11824 SaveGameOldStyle(f)
11825      FILE *f;
11826 {
11827     int i, offset;
11828     time_t tm;
11829
11830     tm = time((time_t *) NULL);
11831
11832     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11833     PrintOpponents(f);
11834
11835     if (backwardMostMove > 0 || startedFromSetupPosition) {
11836         fprintf(f, "\n[--------------\n");
11837         PrintPosition(f, backwardMostMove);
11838         fprintf(f, "--------------]\n");
11839     } else {
11840         fprintf(f, "\n");
11841     }
11842
11843     i = backwardMostMove;
11844     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11845
11846     while (i < forwardMostMove) {
11847         if (commentList[i] != NULL) {
11848             fprintf(f, "[%s]\n", commentList[i]);
11849         }
11850
11851         if ((i % 2) == 1) {
11852             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11853             i++;
11854         } else {
11855             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11856             i++;
11857             if (commentList[i] != NULL) {
11858                 fprintf(f, "\n");
11859                 continue;
11860             }
11861             if (i >= forwardMostMove) {
11862                 fprintf(f, "\n");
11863                 break;
11864             }
11865             fprintf(f, "%s\n", parseList[i]);
11866             i++;
11867         }
11868     }
11869
11870     if (commentList[i] != NULL) {
11871         fprintf(f, "[%s]\n", commentList[i]);
11872     }
11873
11874     /* This isn't really the old style, but it's close enough */
11875     if (gameInfo.resultDetails != NULL &&
11876         gameInfo.resultDetails[0] != NULLCHAR) {
11877         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11878                 gameInfo.resultDetails);
11879     } else {
11880         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11881     }
11882
11883     fclose(f);
11884     return TRUE;
11885 }
11886
11887 /* Save the current game to open file f and close the file */
11888 int
11889 SaveGame(f, dummy, dummy2)
11890      FILE *f;
11891      int dummy;
11892      char *dummy2;
11893 {
11894     if (gameMode == EditPosition) EditPositionDone(TRUE);
11895     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11896     if (appData.oldSaveStyle)
11897       return SaveGameOldStyle(f);
11898     else
11899       return SaveGamePGN(f);
11900 }
11901
11902 /* Save the current position to the given file */
11903 int
11904 SavePositionToFile(filename)
11905      char *filename;
11906 {
11907     FILE *f;
11908     char buf[MSG_SIZ];
11909
11910     if (strcmp(filename, "-") == 0) {
11911         return SavePosition(stdout, 0, NULL);
11912     } else {
11913         f = fopen(filename, "a");
11914         if (f == NULL) {
11915             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11916             DisplayError(buf, errno);
11917             return FALSE;
11918         } else {
11919             safeStrCpy(buf, lastMsg, MSG_SIZ);
11920             DisplayMessage(_("Waiting for access to save file"), "");
11921             flock(fileno(f), LOCK_EX); // [HGM] lock
11922             DisplayMessage(_("Saving position"), "");
11923             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11924             SavePosition(f, 0, NULL);
11925             DisplayMessage(buf, "");
11926             return TRUE;
11927         }
11928     }
11929 }
11930
11931 /* Save the current position to the given open file and close the file */
11932 int
11933 SavePosition(f, dummy, dummy2)
11934      FILE *f;
11935      int dummy;
11936      char *dummy2;
11937 {
11938     time_t tm;
11939     char *fen;
11940
11941     if (gameMode == EditPosition) EditPositionDone(TRUE);
11942     if (appData.oldSaveStyle) {
11943         tm = time((time_t *) NULL);
11944
11945         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11946         PrintOpponents(f);
11947         fprintf(f, "[--------------\n");
11948         PrintPosition(f, currentMove);
11949         fprintf(f, "--------------]\n");
11950     } else {
11951         fen = PositionToFEN(currentMove, NULL);
11952         fprintf(f, "%s\n", fen);
11953         free(fen);
11954     }
11955     fclose(f);
11956     return TRUE;
11957 }
11958
11959 void
11960 ReloadCmailMsgEvent(unregister)
11961      int unregister;
11962 {
11963 #if !WIN32
11964     static char *inFilename = NULL;
11965     static char *outFilename;
11966     int i;
11967     struct stat inbuf, outbuf;
11968     int status;
11969
11970     /* Any registered moves are unregistered if unregister is set, */
11971     /* i.e. invoked by the signal handler */
11972     if (unregister) {
11973         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11974             cmailMoveRegistered[i] = FALSE;
11975             if (cmailCommentList[i] != NULL) {
11976                 free(cmailCommentList[i]);
11977                 cmailCommentList[i] = NULL;
11978             }
11979         }
11980         nCmailMovesRegistered = 0;
11981     }
11982
11983     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11984         cmailResult[i] = CMAIL_NOT_RESULT;
11985     }
11986     nCmailResults = 0;
11987
11988     if (inFilename == NULL) {
11989         /* Because the filenames are static they only get malloced once  */
11990         /* and they never get freed                                      */
11991         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11992         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11993
11994         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11995         sprintf(outFilename, "%s.out", appData.cmailGameName);
11996     }
11997
11998     status = stat(outFilename, &outbuf);
11999     if (status < 0) {
12000         cmailMailedMove = FALSE;
12001     } else {
12002         status = stat(inFilename, &inbuf);
12003         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12004     }
12005
12006     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12007        counts the games, notes how each one terminated, etc.
12008
12009        It would be nice to remove this kludge and instead gather all
12010        the information while building the game list.  (And to keep it
12011        in the game list nodes instead of having a bunch of fixed-size
12012        parallel arrays.)  Note this will require getting each game's
12013        termination from the PGN tags, as the game list builder does
12014        not process the game moves.  --mann
12015        */
12016     cmailMsgLoaded = TRUE;
12017     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12018
12019     /* Load first game in the file or popup game menu */
12020     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12021
12022 #endif /* !WIN32 */
12023     return;
12024 }
12025
12026 int
12027 RegisterMove()
12028 {
12029     FILE *f;
12030     char string[MSG_SIZ];
12031
12032     if (   cmailMailedMove
12033         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12034         return TRUE;            /* Allow free viewing  */
12035     }
12036
12037     /* Unregister move to ensure that we don't leave RegisterMove        */
12038     /* with the move registered when the conditions for registering no   */
12039     /* longer hold                                                       */
12040     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12041         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12042         nCmailMovesRegistered --;
12043
12044         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12045           {
12046               free(cmailCommentList[lastLoadGameNumber - 1]);
12047               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12048           }
12049     }
12050
12051     if (cmailOldMove == -1) {
12052         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12053         return FALSE;
12054     }
12055
12056     if (currentMove > cmailOldMove + 1) {
12057         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12058         return FALSE;
12059     }
12060
12061     if (currentMove < cmailOldMove) {
12062         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12063         return FALSE;
12064     }
12065
12066     if (forwardMostMove > currentMove) {
12067         /* Silently truncate extra moves */
12068         TruncateGame();
12069     }
12070
12071     if (   (currentMove == cmailOldMove + 1)
12072         || (   (currentMove == cmailOldMove)
12073             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12074                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12075         if (gameInfo.result != GameUnfinished) {
12076             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12077         }
12078
12079         if (commentList[currentMove] != NULL) {
12080             cmailCommentList[lastLoadGameNumber - 1]
12081               = StrSave(commentList[currentMove]);
12082         }
12083         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12084
12085         if (appData.debugMode)
12086           fprintf(debugFP, "Saving %s for game %d\n",
12087                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12088
12089         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12090
12091         f = fopen(string, "w");
12092         if (appData.oldSaveStyle) {
12093             SaveGameOldStyle(f); /* also closes the file */
12094
12095             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12096             f = fopen(string, "w");
12097             SavePosition(f, 0, NULL); /* also closes the file */
12098         } else {
12099             fprintf(f, "{--------------\n");
12100             PrintPosition(f, currentMove);
12101             fprintf(f, "--------------}\n\n");
12102
12103             SaveGame(f, 0, NULL); /* also closes the file*/
12104         }
12105
12106         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12107         nCmailMovesRegistered ++;
12108     } else if (nCmailGames == 1) {
12109         DisplayError(_("You have not made a move yet"), 0);
12110         return FALSE;
12111     }
12112
12113     return TRUE;
12114 }
12115
12116 void
12117 MailMoveEvent()
12118 {
12119 #if !WIN32
12120     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12121     FILE *commandOutput;
12122     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12123     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12124     int nBuffers;
12125     int i;
12126     int archived;
12127     char *arcDir;
12128
12129     if (! cmailMsgLoaded) {
12130         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12131         return;
12132     }
12133
12134     if (nCmailGames == nCmailResults) {
12135         DisplayError(_("No unfinished games"), 0);
12136         return;
12137     }
12138
12139 #if CMAIL_PROHIBIT_REMAIL
12140     if (cmailMailedMove) {
12141       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);
12142         DisplayError(msg, 0);
12143         return;
12144     }
12145 #endif
12146
12147     if (! (cmailMailedMove || RegisterMove())) return;
12148
12149     if (   cmailMailedMove
12150         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12151       snprintf(string, MSG_SIZ, partCommandString,
12152                appData.debugMode ? " -v" : "", appData.cmailGameName);
12153         commandOutput = popen(string, "r");
12154
12155         if (commandOutput == NULL) {
12156             DisplayError(_("Failed to invoke cmail"), 0);
12157         } else {
12158             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12159                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12160             }
12161             if (nBuffers > 1) {
12162                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12163                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12164                 nBytes = MSG_SIZ - 1;
12165             } else {
12166                 (void) memcpy(msg, buffer, nBytes);
12167             }
12168             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12169
12170             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12171                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12172
12173                 archived = TRUE;
12174                 for (i = 0; i < nCmailGames; i ++) {
12175                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12176                         archived = FALSE;
12177                     }
12178                 }
12179                 if (   archived
12180                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12181                         != NULL)) {
12182                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12183                            arcDir,
12184                            appData.cmailGameName,
12185                            gameInfo.date);
12186                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12187                     cmailMsgLoaded = FALSE;
12188                 }
12189             }
12190
12191             DisplayInformation(msg);
12192             pclose(commandOutput);
12193         }
12194     } else {
12195         if ((*cmailMsg) != '\0') {
12196             DisplayInformation(cmailMsg);
12197         }
12198     }
12199
12200     return;
12201 #endif /* !WIN32 */
12202 }
12203
12204 char *
12205 CmailMsg()
12206 {
12207 #if WIN32
12208     return NULL;
12209 #else
12210     int  prependComma = 0;
12211     char number[5];
12212     char string[MSG_SIZ];       /* Space for game-list */
12213     int  i;
12214
12215     if (!cmailMsgLoaded) return "";
12216
12217     if (cmailMailedMove) {
12218       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12219     } else {
12220         /* Create a list of games left */
12221       snprintf(string, MSG_SIZ, "[");
12222         for (i = 0; i < nCmailGames; i ++) {
12223             if (! (   cmailMoveRegistered[i]
12224                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12225                 if (prependComma) {
12226                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12227                 } else {
12228                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12229                     prependComma = 1;
12230                 }
12231
12232                 strcat(string, number);
12233             }
12234         }
12235         strcat(string, "]");
12236
12237         if (nCmailMovesRegistered + nCmailResults == 0) {
12238             switch (nCmailGames) {
12239               case 1:
12240                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12241                 break;
12242
12243               case 2:
12244                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12245                 break;
12246
12247               default:
12248                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12249                          nCmailGames);
12250                 break;
12251             }
12252         } else {
12253             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12254               case 1:
12255                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12256                          string);
12257                 break;
12258
12259               case 0:
12260                 if (nCmailResults == nCmailGames) {
12261                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12262                 } else {
12263                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12264                 }
12265                 break;
12266
12267               default:
12268                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12269                          string);
12270             }
12271         }
12272     }
12273     return cmailMsg;
12274 #endif /* WIN32 */
12275 }
12276
12277 void
12278 ResetGameEvent()
12279 {
12280     if (gameMode == Training)
12281       SetTrainingModeOff();
12282
12283     Reset(TRUE, TRUE);
12284     cmailMsgLoaded = FALSE;
12285     if (appData.icsActive) {
12286       SendToICS(ics_prefix);
12287       SendToICS("refresh\n");
12288     }
12289 }
12290
12291 void
12292 ExitEvent(status)
12293      int status;
12294 {
12295     exiting++;
12296     if (exiting > 2) {
12297       /* Give up on clean exit */
12298       exit(status);
12299     }
12300     if (exiting > 1) {
12301       /* Keep trying for clean exit */
12302       return;
12303     }
12304
12305     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12306
12307     if (telnetISR != NULL) {
12308       RemoveInputSource(telnetISR);
12309     }
12310     if (icsPR != NoProc) {
12311       DestroyChildProcess(icsPR, TRUE);
12312     }
12313
12314     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12315     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12316
12317     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12318     /* make sure this other one finishes before killing it!                  */
12319     if(endingGame) { int count = 0;
12320         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12321         while(endingGame && count++ < 10) DoSleep(1);
12322         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12323     }
12324
12325     /* Kill off chess programs */
12326     if (first.pr != NoProc) {
12327         ExitAnalyzeMode();
12328
12329         DoSleep( appData.delayBeforeQuit );
12330         SendToProgram("quit\n", &first);
12331         DoSleep( appData.delayAfterQuit );
12332         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12333     }
12334     if (second.pr != NoProc) {
12335         DoSleep( appData.delayBeforeQuit );
12336         SendToProgram("quit\n", &second);
12337         DoSleep( appData.delayAfterQuit );
12338         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12339     }
12340     if (first.isr != NULL) {
12341         RemoveInputSource(first.isr);
12342     }
12343     if (second.isr != NULL) {
12344         RemoveInputSource(second.isr);
12345     }
12346
12347     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12348     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12349
12350     ShutDownFrontEnd();
12351     exit(status);
12352 }
12353
12354 void
12355 PauseEvent()
12356 {
12357     if (appData.debugMode)
12358         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12359     if (pausing) {
12360         pausing = FALSE;
12361         ModeHighlight();
12362         if (gameMode == MachinePlaysWhite ||
12363             gameMode == MachinePlaysBlack) {
12364             StartClocks();
12365         } else {
12366             DisplayBothClocks();
12367         }
12368         if (gameMode == PlayFromGameFile) {
12369             if (appData.timeDelay >= 0)
12370                 AutoPlayGameLoop();
12371         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12372             Reset(FALSE, TRUE);
12373             SendToICS(ics_prefix);
12374             SendToICS("refresh\n");
12375         } else if (currentMove < forwardMostMove) {
12376             ForwardInner(forwardMostMove);
12377         }
12378         pauseExamInvalid = FALSE;
12379     } else {
12380         switch (gameMode) {
12381           default:
12382             return;
12383           case IcsExamining:
12384             pauseExamForwardMostMove = forwardMostMove;
12385             pauseExamInvalid = FALSE;
12386             /* fall through */
12387           case IcsObserving:
12388           case IcsPlayingWhite:
12389           case IcsPlayingBlack:
12390             pausing = TRUE;
12391             ModeHighlight();
12392             return;
12393           case PlayFromGameFile:
12394             (void) StopLoadGameTimer();
12395             pausing = TRUE;
12396             ModeHighlight();
12397             break;
12398           case BeginningOfGame:
12399             if (appData.icsActive) return;
12400             /* else fall through */
12401           case MachinePlaysWhite:
12402           case MachinePlaysBlack:
12403           case TwoMachinesPlay:
12404             if (forwardMostMove == 0)
12405               return;           /* don't pause if no one has moved */
12406             if ((gameMode == MachinePlaysWhite &&
12407                  !WhiteOnMove(forwardMostMove)) ||
12408                 (gameMode == MachinePlaysBlack &&
12409                  WhiteOnMove(forwardMostMove))) {
12410                 StopClocks();
12411             }
12412             pausing = TRUE;
12413             ModeHighlight();
12414             break;
12415         }
12416     }
12417 }
12418
12419 void
12420 EditCommentEvent()
12421 {
12422     char title[MSG_SIZ];
12423
12424     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12425       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12426     } else {
12427       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12428                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12429                parseList[currentMove - 1]);
12430     }
12431
12432     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12433 }
12434
12435
12436 void
12437 EditTagsEvent()
12438 {
12439     char *tags = PGNTags(&gameInfo);
12440     bookUp = FALSE;
12441     EditTagsPopUp(tags, NULL);
12442     free(tags);
12443 }
12444
12445 void
12446 AnalyzeModeEvent()
12447 {
12448     if (appData.noChessProgram || gameMode == AnalyzeMode)
12449       return;
12450
12451     if (gameMode != AnalyzeFile) {
12452         if (!appData.icsEngineAnalyze) {
12453                EditGameEvent();
12454                if (gameMode != EditGame) return;
12455         }
12456         ResurrectChessProgram();
12457         SendToProgram("analyze\n", &first);
12458         first.analyzing = TRUE;
12459         /*first.maybeThinking = TRUE;*/
12460         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12461         EngineOutputPopUp();
12462     }
12463     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12464     pausing = FALSE;
12465     ModeHighlight();
12466     SetGameInfo();
12467
12468     StartAnalysisClock();
12469     GetTimeMark(&lastNodeCountTime);
12470     lastNodeCount = 0;
12471 }
12472
12473 void
12474 AnalyzeFileEvent()
12475 {
12476     if (appData.noChessProgram || gameMode == AnalyzeFile)
12477       return;
12478
12479     if (gameMode != AnalyzeMode) {
12480         EditGameEvent();
12481         if (gameMode != EditGame) return;
12482         ResurrectChessProgram();
12483         SendToProgram("analyze\n", &first);
12484         first.analyzing = TRUE;
12485         /*first.maybeThinking = TRUE;*/
12486         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12487         EngineOutputPopUp();
12488     }
12489     gameMode = AnalyzeFile;
12490     pausing = FALSE;
12491     ModeHighlight();
12492     SetGameInfo();
12493
12494     StartAnalysisClock();
12495     GetTimeMark(&lastNodeCountTime);
12496     lastNodeCount = 0;
12497 }
12498
12499 void
12500 MachineWhiteEvent()
12501 {
12502     char buf[MSG_SIZ];
12503     char *bookHit = NULL;
12504
12505     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12506       return;
12507
12508
12509     if (gameMode == PlayFromGameFile ||
12510         gameMode == TwoMachinesPlay  ||
12511         gameMode == Training         ||
12512         gameMode == AnalyzeMode      ||
12513         gameMode == EndOfGame)
12514         EditGameEvent();
12515
12516     if (gameMode == EditPosition)
12517         EditPositionDone(TRUE);
12518
12519     if (!WhiteOnMove(currentMove)) {
12520         DisplayError(_("It is not White's turn"), 0);
12521         return;
12522     }
12523
12524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12525       ExitAnalyzeMode();
12526
12527     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12528         gameMode == AnalyzeFile)
12529         TruncateGame();
12530
12531     ResurrectChessProgram();    /* in case it isn't running */
12532     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12533         gameMode = MachinePlaysWhite;
12534         ResetClocks();
12535     } else
12536     gameMode = MachinePlaysWhite;
12537     pausing = FALSE;
12538     ModeHighlight();
12539     SetGameInfo();
12540     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12541     DisplayTitle(buf);
12542     if (first.sendName) {
12543       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12544       SendToProgram(buf, &first);
12545     }
12546     if (first.sendTime) {
12547       if (first.useColors) {
12548         SendToProgram("black\n", &first); /*gnu kludge*/
12549       }
12550       SendTimeRemaining(&first, TRUE);
12551     }
12552     if (first.useColors) {
12553       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12554     }
12555     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12556     SetMachineThinkingEnables();
12557     first.maybeThinking = TRUE;
12558     StartClocks();
12559     firstMove = FALSE;
12560
12561     if (appData.autoFlipView && !flipView) {
12562       flipView = !flipView;
12563       DrawPosition(FALSE, NULL);
12564       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12565     }
12566
12567     if(bookHit) { // [HGM] book: simulate book reply
12568         static char bookMove[MSG_SIZ]; // a bit generous?
12569
12570         programStats.nodes = programStats.depth = programStats.time =
12571         programStats.score = programStats.got_only_move = 0;
12572         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12573
12574         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12575         strcat(bookMove, bookHit);
12576         HandleMachineMove(bookMove, &first);
12577     }
12578 }
12579
12580 void
12581 MachineBlackEvent()
12582 {
12583   char buf[MSG_SIZ];
12584   char *bookHit = NULL;
12585
12586     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12587         return;
12588
12589
12590     if (gameMode == PlayFromGameFile ||
12591         gameMode == TwoMachinesPlay  ||
12592         gameMode == Training         ||
12593         gameMode == AnalyzeMode      ||
12594         gameMode == EndOfGame)
12595         EditGameEvent();
12596
12597     if (gameMode == EditPosition)
12598         EditPositionDone(TRUE);
12599
12600     if (WhiteOnMove(currentMove)) {
12601         DisplayError(_("It is not Black's turn"), 0);
12602         return;
12603     }
12604
12605     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12606       ExitAnalyzeMode();
12607
12608     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12609         gameMode == AnalyzeFile)
12610         TruncateGame();
12611
12612     ResurrectChessProgram();    /* in case it isn't running */
12613     gameMode = MachinePlaysBlack;
12614     pausing = FALSE;
12615     ModeHighlight();
12616     SetGameInfo();
12617     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12618     DisplayTitle(buf);
12619     if (first.sendName) {
12620       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12621       SendToProgram(buf, &first);
12622     }
12623     if (first.sendTime) {
12624       if (first.useColors) {
12625         SendToProgram("white\n", &first); /*gnu kludge*/
12626       }
12627       SendTimeRemaining(&first, FALSE);
12628     }
12629     if (first.useColors) {
12630       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12631     }
12632     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12633     SetMachineThinkingEnables();
12634     first.maybeThinking = TRUE;
12635     StartClocks();
12636
12637     if (appData.autoFlipView && flipView) {
12638       flipView = !flipView;
12639       DrawPosition(FALSE, NULL);
12640       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12641     }
12642     if(bookHit) { // [HGM] book: simulate book reply
12643         static char bookMove[MSG_SIZ]; // a bit generous?
12644
12645         programStats.nodes = programStats.depth = programStats.time =
12646         programStats.score = programStats.got_only_move = 0;
12647         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12648
12649         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12650         strcat(bookMove, bookHit);
12651         HandleMachineMove(bookMove, &first);
12652     }
12653 }
12654
12655
12656 void
12657 DisplayTwoMachinesTitle()
12658 {
12659     char buf[MSG_SIZ];
12660     if (appData.matchGames > 0) {
12661         if (first.twoMachinesColor[0] == 'w') {
12662           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12663                    gameInfo.white, gameInfo.black,
12664                    first.matchWins, second.matchWins,
12665                    matchGame - 1 - (first.matchWins + second.matchWins));
12666         } else {
12667           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12668                    gameInfo.white, gameInfo.black,
12669                    second.matchWins, first.matchWins,
12670                    matchGame - 1 - (first.matchWins + second.matchWins));
12671         }
12672     } else {
12673       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12674     }
12675     DisplayTitle(buf);
12676 }
12677
12678 void
12679 SettingsMenuIfReady()
12680 {
12681   if (second.lastPing != second.lastPong) {
12682     DisplayMessage("", _("Waiting for second chess program"));
12683     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12684     return;
12685   }
12686   ThawUI();
12687   DisplayMessage("", "");
12688   SettingsPopUp(&second);
12689 }
12690
12691 int
12692 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12693 {
12694     char buf[MSG_SIZ];
12695     if (cps->pr == NULL) {
12696         StartChessProgram(cps);
12697         if (cps->protocolVersion == 1) {
12698           retry();
12699         } else {
12700           /* kludge: allow timeout for initial "feature" command */
12701           FreezeUI();
12702           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12703           DisplayMessage("", buf);
12704           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12705         }
12706         return 1;
12707     }
12708     return 0;
12709 }
12710
12711 void
12712 TwoMachinesEvent P((void))
12713 {
12714     int i;
12715     char buf[MSG_SIZ];
12716     ChessProgramState *onmove;
12717     char *bookHit = NULL;
12718     static int stalling = 0;
12719     TimeMark now;
12720     long wait;
12721
12722     if (appData.noChessProgram) return;
12723
12724     switch (gameMode) {
12725       case TwoMachinesPlay:
12726         return;
12727       case MachinePlaysWhite:
12728       case MachinePlaysBlack:
12729         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12730             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12731             return;
12732         }
12733         /* fall through */
12734       case BeginningOfGame:
12735       case PlayFromGameFile:
12736       case EndOfGame:
12737         EditGameEvent();
12738         if (gameMode != EditGame) return;
12739         break;
12740       case EditPosition:
12741         EditPositionDone(TRUE);
12742         break;
12743       case AnalyzeMode:
12744       case AnalyzeFile:
12745         ExitAnalyzeMode();
12746         break;
12747       case EditGame:
12748       default:
12749         break;
12750     }
12751
12752 //    forwardMostMove = currentMove;
12753     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12754
12755     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12756
12757     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12758     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12759       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12760       return;
12761     }
12762     if(!stalling) {
12763       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12764       SendToProgram("force\n", &second);
12765       stalling = 1;
12766       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12767       return;
12768     }
12769     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12770     if(appData.matchPause>10000 || appData.matchPause<10)
12771                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12772     wait = SubtractTimeMarks(&now, &pauseStart);
12773     if(wait < appData.matchPause) {
12774         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12775         return;
12776     }
12777     stalling = 0;
12778     DisplayMessage("", "");
12779     if (startedFromSetupPosition) {
12780         SendBoard(&second, backwardMostMove);
12781     if (appData.debugMode) {
12782         fprintf(debugFP, "Two Machines\n");
12783     }
12784     }
12785     for (i = backwardMostMove; i < forwardMostMove; i++) {
12786         SendMoveToProgram(i, &second);
12787     }
12788
12789     gameMode = TwoMachinesPlay;
12790     pausing = FALSE;
12791     ModeHighlight();
12792     SetGameInfo();
12793     DisplayTwoMachinesTitle();
12794     firstMove = TRUE;
12795     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12796         onmove = &first;
12797     } else {
12798         onmove = &second;
12799     }
12800     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12801     SendToProgram(first.computerString, &first);
12802     if (first.sendName) {
12803       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12804       SendToProgram(buf, &first);
12805     }
12806     SendToProgram(second.computerString, &second);
12807     if (second.sendName) {
12808       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12809       SendToProgram(buf, &second);
12810     }
12811
12812     ResetClocks();
12813     if (!first.sendTime || !second.sendTime) {
12814         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12815         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12816     }
12817     if (onmove->sendTime) {
12818       if (onmove->useColors) {
12819         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12820       }
12821       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12822     }
12823     if (onmove->useColors) {
12824       SendToProgram(onmove->twoMachinesColor, onmove);
12825     }
12826     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12827 //    SendToProgram("go\n", onmove);
12828     onmove->maybeThinking = TRUE;
12829     SetMachineThinkingEnables();
12830
12831     StartClocks();
12832
12833     if(bookHit) { // [HGM] book: simulate book reply
12834         static char bookMove[MSG_SIZ]; // a bit generous?
12835
12836         programStats.nodes = programStats.depth = programStats.time =
12837         programStats.score = programStats.got_only_move = 0;
12838         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12839
12840         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12841         strcat(bookMove, bookHit);
12842         savedMessage = bookMove; // args for deferred call
12843         savedState = onmove;
12844         ScheduleDelayedEvent(DeferredBookMove, 1);
12845     }
12846 }
12847
12848 void
12849 TrainingEvent()
12850 {
12851     if (gameMode == Training) {
12852       SetTrainingModeOff();
12853       gameMode = PlayFromGameFile;
12854       DisplayMessage("", _("Training mode off"));
12855     } else {
12856       gameMode = Training;
12857       animateTraining = appData.animate;
12858
12859       /* make sure we are not already at the end of the game */
12860       if (currentMove < forwardMostMove) {
12861         SetTrainingModeOn();
12862         DisplayMessage("", _("Training mode on"));
12863       } else {
12864         gameMode = PlayFromGameFile;
12865         DisplayError(_("Already at end of game"), 0);
12866       }
12867     }
12868     ModeHighlight();
12869 }
12870
12871 void
12872 IcsClientEvent()
12873 {
12874     if (!appData.icsActive) return;
12875     switch (gameMode) {
12876       case IcsPlayingWhite:
12877       case IcsPlayingBlack:
12878       case IcsObserving:
12879       case IcsIdle:
12880       case BeginningOfGame:
12881       case IcsExamining:
12882         return;
12883
12884       case EditGame:
12885         break;
12886
12887       case EditPosition:
12888         EditPositionDone(TRUE);
12889         break;
12890
12891       case AnalyzeMode:
12892       case AnalyzeFile:
12893         ExitAnalyzeMode();
12894         break;
12895
12896       default:
12897         EditGameEvent();
12898         break;
12899     }
12900
12901     gameMode = IcsIdle;
12902     ModeHighlight();
12903     return;
12904 }
12905
12906
12907 void
12908 EditGameEvent()
12909 {
12910     int i;
12911
12912     switch (gameMode) {
12913       case Training:
12914         SetTrainingModeOff();
12915         break;
12916       case MachinePlaysWhite:
12917       case MachinePlaysBlack:
12918       case BeginningOfGame:
12919         SendToProgram("force\n", &first);
12920         SetUserThinkingEnables();
12921         break;
12922       case PlayFromGameFile:
12923         (void) StopLoadGameTimer();
12924         if (gameFileFP != NULL) {
12925             gameFileFP = NULL;
12926         }
12927         break;
12928       case EditPosition:
12929         EditPositionDone(TRUE);
12930         break;
12931       case AnalyzeMode:
12932       case AnalyzeFile:
12933         ExitAnalyzeMode();
12934         SendToProgram("force\n", &first);
12935         break;
12936       case TwoMachinesPlay:
12937         GameEnds(EndOfFile, NULL, GE_PLAYER);
12938         ResurrectChessProgram();
12939         SetUserThinkingEnables();
12940         break;
12941       case EndOfGame:
12942         ResurrectChessProgram();
12943         break;
12944       case IcsPlayingBlack:
12945       case IcsPlayingWhite:
12946         DisplayError(_("Warning: You are still playing a game"), 0);
12947         break;
12948       case IcsObserving:
12949         DisplayError(_("Warning: You are still observing a game"), 0);
12950         break;
12951       case IcsExamining:
12952         DisplayError(_("Warning: You are still examining a game"), 0);
12953         break;
12954       case IcsIdle:
12955         break;
12956       case EditGame:
12957       default:
12958         return;
12959     }
12960
12961     pausing = FALSE;
12962     StopClocks();
12963     first.offeredDraw = second.offeredDraw = 0;
12964
12965     if (gameMode == PlayFromGameFile) {
12966         whiteTimeRemaining = timeRemaining[0][currentMove];
12967         blackTimeRemaining = timeRemaining[1][currentMove];
12968         DisplayTitle("");
12969     }
12970
12971     if (gameMode == MachinePlaysWhite ||
12972         gameMode == MachinePlaysBlack ||
12973         gameMode == TwoMachinesPlay ||
12974         gameMode == EndOfGame) {
12975         i = forwardMostMove;
12976         while (i > currentMove) {
12977             SendToProgram("undo\n", &first);
12978             i--;
12979         }
12980         whiteTimeRemaining = timeRemaining[0][currentMove];
12981         blackTimeRemaining = timeRemaining[1][currentMove];
12982         DisplayBothClocks();
12983         if (whiteFlag || blackFlag) {
12984             whiteFlag = blackFlag = 0;
12985         }
12986         DisplayTitle("");
12987     }
12988
12989     gameMode = EditGame;
12990     ModeHighlight();
12991     SetGameInfo();
12992 }
12993
12994
12995 void
12996 EditPositionEvent()
12997 {
12998     if (gameMode == EditPosition) {
12999         EditGameEvent();
13000         return;
13001     }
13002
13003     EditGameEvent();
13004     if (gameMode != EditGame) return;
13005
13006     gameMode = EditPosition;
13007     ModeHighlight();
13008     SetGameInfo();
13009     if (currentMove > 0)
13010       CopyBoard(boards[0], boards[currentMove]);
13011
13012     blackPlaysFirst = !WhiteOnMove(currentMove);
13013     ResetClocks();
13014     currentMove = forwardMostMove = backwardMostMove = 0;
13015     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13016     DisplayMove(-1);
13017 }
13018
13019 void
13020 ExitAnalyzeMode()
13021 {
13022     /* [DM] icsEngineAnalyze - possible call from other functions */
13023     if (appData.icsEngineAnalyze) {
13024         appData.icsEngineAnalyze = FALSE;
13025
13026         DisplayMessage("",_("Close ICS engine analyze..."));
13027     }
13028     if (first.analysisSupport && first.analyzing) {
13029       SendToProgram("exit\n", &first);
13030       first.analyzing = FALSE;
13031     }
13032     thinkOutput[0] = NULLCHAR;
13033 }
13034
13035 void
13036 EditPositionDone(Boolean fakeRights)
13037 {
13038     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13039
13040     startedFromSetupPosition = TRUE;
13041     InitChessProgram(&first, FALSE);
13042     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13043       boards[0][EP_STATUS] = EP_NONE;
13044       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13045     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13046         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13047         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13048       } else boards[0][CASTLING][2] = NoRights;
13049     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13050         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13051         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13052       } else boards[0][CASTLING][5] = NoRights;
13053     }
13054     SendToProgram("force\n", &first);
13055     if (blackPlaysFirst) {
13056         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13057         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13058         currentMove = forwardMostMove = backwardMostMove = 1;
13059         CopyBoard(boards[1], boards[0]);
13060     } else {
13061         currentMove = forwardMostMove = backwardMostMove = 0;
13062     }
13063     SendBoard(&first, forwardMostMove);
13064     if (appData.debugMode) {
13065         fprintf(debugFP, "EditPosDone\n");
13066     }
13067     DisplayTitle("");
13068     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13069     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13070     gameMode = EditGame;
13071     ModeHighlight();
13072     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13073     ClearHighlights(); /* [AS] */
13074 }
13075
13076 /* Pause for `ms' milliseconds */
13077 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13078 void
13079 TimeDelay(ms)
13080      long ms;
13081 {
13082     TimeMark m1, m2;
13083
13084     GetTimeMark(&m1);
13085     do {
13086         GetTimeMark(&m2);
13087     } while (SubtractTimeMarks(&m2, &m1) < ms);
13088 }
13089
13090 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13091 void
13092 SendMultiLineToICS(buf)
13093      char *buf;
13094 {
13095     char temp[MSG_SIZ+1], *p;
13096     int len;
13097
13098     len = strlen(buf);
13099     if (len > MSG_SIZ)
13100       len = MSG_SIZ;
13101
13102     strncpy(temp, buf, len);
13103     temp[len] = 0;
13104
13105     p = temp;
13106     while (*p) {
13107         if (*p == '\n' || *p == '\r')
13108           *p = ' ';
13109         ++p;
13110     }
13111
13112     strcat(temp, "\n");
13113     SendToICS(temp);
13114     SendToPlayer(temp, strlen(temp));
13115 }
13116
13117 void
13118 SetWhiteToPlayEvent()
13119 {
13120     if (gameMode == EditPosition) {
13121         blackPlaysFirst = FALSE;
13122         DisplayBothClocks();    /* works because currentMove is 0 */
13123     } else if (gameMode == IcsExamining) {
13124         SendToICS(ics_prefix);
13125         SendToICS("tomove white\n");
13126     }
13127 }
13128
13129 void
13130 SetBlackToPlayEvent()
13131 {
13132     if (gameMode == EditPosition) {
13133         blackPlaysFirst = TRUE;
13134         currentMove = 1;        /* kludge */
13135         DisplayBothClocks();
13136         currentMove = 0;
13137     } else if (gameMode == IcsExamining) {
13138         SendToICS(ics_prefix);
13139         SendToICS("tomove black\n");
13140     }
13141 }
13142
13143 void
13144 EditPositionMenuEvent(selection, x, y)
13145      ChessSquare selection;
13146      int x, y;
13147 {
13148     char buf[MSG_SIZ];
13149     ChessSquare piece = boards[0][y][x];
13150
13151     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13152
13153     switch (selection) {
13154       case ClearBoard:
13155         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13156             SendToICS(ics_prefix);
13157             SendToICS("bsetup clear\n");
13158         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13159             SendToICS(ics_prefix);
13160             SendToICS("clearboard\n");
13161         } else {
13162             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13163                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13164                 for (y = 0; y < BOARD_HEIGHT; y++) {
13165                     if (gameMode == IcsExamining) {
13166                         if (boards[currentMove][y][x] != EmptySquare) {
13167                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13168                                     AAA + x, ONE + y);
13169                             SendToICS(buf);
13170                         }
13171                     } else {
13172                         boards[0][y][x] = p;
13173                     }
13174                 }
13175             }
13176         }
13177         if (gameMode == EditPosition) {
13178             DrawPosition(FALSE, boards[0]);
13179         }
13180         break;
13181
13182       case WhitePlay:
13183         SetWhiteToPlayEvent();
13184         break;
13185
13186       case BlackPlay:
13187         SetBlackToPlayEvent();
13188         break;
13189
13190       case EmptySquare:
13191         if (gameMode == IcsExamining) {
13192             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13193             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13194             SendToICS(buf);
13195         } else {
13196             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13197                 if(x == BOARD_LEFT-2) {
13198                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13199                     boards[0][y][1] = 0;
13200                 } else
13201                 if(x == BOARD_RGHT+1) {
13202                     if(y >= gameInfo.holdingsSize) break;
13203                     boards[0][y][BOARD_WIDTH-2] = 0;
13204                 } else break;
13205             }
13206             boards[0][y][x] = EmptySquare;
13207             DrawPosition(FALSE, boards[0]);
13208         }
13209         break;
13210
13211       case PromotePiece:
13212         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13213            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13214             selection = (ChessSquare) (PROMOTED piece);
13215         } else if(piece == EmptySquare) selection = WhiteSilver;
13216         else selection = (ChessSquare)((int)piece - 1);
13217         goto defaultlabel;
13218
13219       case DemotePiece:
13220         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13221            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13222             selection = (ChessSquare) (DEMOTED piece);
13223         } else if(piece == EmptySquare) selection = BlackSilver;
13224         else selection = (ChessSquare)((int)piece + 1);
13225         goto defaultlabel;
13226
13227       case WhiteQueen:
13228       case BlackQueen:
13229         if(gameInfo.variant == VariantShatranj ||
13230            gameInfo.variant == VariantXiangqi  ||
13231            gameInfo.variant == VariantCourier  ||
13232            gameInfo.variant == VariantMakruk     )
13233             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13234         goto defaultlabel;
13235
13236       case WhiteKing:
13237       case BlackKing:
13238         if(gameInfo.variant == VariantXiangqi)
13239             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13240         if(gameInfo.variant == VariantKnightmate)
13241             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13242       default:
13243         defaultlabel:
13244         if (gameMode == IcsExamining) {
13245             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13246             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13247                      PieceToChar(selection), AAA + x, ONE + y);
13248             SendToICS(buf);
13249         } else {
13250             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13251                 int n;
13252                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13253                     n = PieceToNumber(selection - BlackPawn);
13254                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13255                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13256                     boards[0][BOARD_HEIGHT-1-n][1]++;
13257                 } else
13258                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13259                     n = PieceToNumber(selection);
13260                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13261                     boards[0][n][BOARD_WIDTH-1] = selection;
13262                     boards[0][n][BOARD_WIDTH-2]++;
13263                 }
13264             } else
13265             boards[0][y][x] = selection;
13266             DrawPosition(TRUE, boards[0]);
13267         }
13268         break;
13269     }
13270 }
13271
13272
13273 void
13274 DropMenuEvent(selection, x, y)
13275      ChessSquare selection;
13276      int x, y;
13277 {
13278     ChessMove moveType;
13279
13280     switch (gameMode) {
13281       case IcsPlayingWhite:
13282       case MachinePlaysBlack:
13283         if (!WhiteOnMove(currentMove)) {
13284             DisplayMoveError(_("It is Black's turn"));
13285             return;
13286         }
13287         moveType = WhiteDrop;
13288         break;
13289       case IcsPlayingBlack:
13290       case MachinePlaysWhite:
13291         if (WhiteOnMove(currentMove)) {
13292             DisplayMoveError(_("It is White's turn"));
13293             return;
13294         }
13295         moveType = BlackDrop;
13296         break;
13297       case EditGame:
13298         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13299         break;
13300       default:
13301         return;
13302     }
13303
13304     if (moveType == BlackDrop && selection < BlackPawn) {
13305       selection = (ChessSquare) ((int) selection
13306                                  + (int) BlackPawn - (int) WhitePawn);
13307     }
13308     if (boards[currentMove][y][x] != EmptySquare) {
13309         DisplayMoveError(_("That square is occupied"));
13310         return;
13311     }
13312
13313     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13314 }
13315
13316 void
13317 AcceptEvent()
13318 {
13319     /* Accept a pending offer of any kind from opponent */
13320
13321     if (appData.icsActive) {
13322         SendToICS(ics_prefix);
13323         SendToICS("accept\n");
13324     } else if (cmailMsgLoaded) {
13325         if (currentMove == cmailOldMove &&
13326             commentList[cmailOldMove] != NULL &&
13327             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13328                    "Black offers a draw" : "White offers a draw")) {
13329             TruncateGame();
13330             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13331             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13332         } else {
13333             DisplayError(_("There is no pending offer on this move"), 0);
13334             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13335         }
13336     } else {
13337         /* Not used for offers from chess program */
13338     }
13339 }
13340
13341 void
13342 DeclineEvent()
13343 {
13344     /* Decline a pending offer of any kind from opponent */
13345
13346     if (appData.icsActive) {
13347         SendToICS(ics_prefix);
13348         SendToICS("decline\n");
13349     } else if (cmailMsgLoaded) {
13350         if (currentMove == cmailOldMove &&
13351             commentList[cmailOldMove] != NULL &&
13352             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13353                    "Black offers a draw" : "White offers a draw")) {
13354 #ifdef NOTDEF
13355             AppendComment(cmailOldMove, "Draw declined", TRUE);
13356             DisplayComment(cmailOldMove - 1, "Draw declined");
13357 #endif /*NOTDEF*/
13358         } else {
13359             DisplayError(_("There is no pending offer on this move"), 0);
13360         }
13361     } else {
13362         /* Not used for offers from chess program */
13363     }
13364 }
13365
13366 void
13367 RematchEvent()
13368 {
13369     /* Issue ICS rematch command */
13370     if (appData.icsActive) {
13371         SendToICS(ics_prefix);
13372         SendToICS("rematch\n");
13373     }
13374 }
13375
13376 void
13377 CallFlagEvent()
13378 {
13379     /* Call your opponent's flag (claim a win on time) */
13380     if (appData.icsActive) {
13381         SendToICS(ics_prefix);
13382         SendToICS("flag\n");
13383     } else {
13384         switch (gameMode) {
13385           default:
13386             return;
13387           case MachinePlaysWhite:
13388             if (whiteFlag) {
13389                 if (blackFlag)
13390                   GameEnds(GameIsDrawn, "Both players ran out of time",
13391                            GE_PLAYER);
13392                 else
13393                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13394             } else {
13395                 DisplayError(_("Your opponent is not out of time"), 0);
13396             }
13397             break;
13398           case MachinePlaysBlack:
13399             if (blackFlag) {
13400                 if (whiteFlag)
13401                   GameEnds(GameIsDrawn, "Both players ran out of time",
13402                            GE_PLAYER);
13403                 else
13404                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13405             } else {
13406                 DisplayError(_("Your opponent is not out of time"), 0);
13407             }
13408             break;
13409         }
13410     }
13411 }
13412
13413 void
13414 ClockClick(int which)
13415 {       // [HGM] code moved to back-end from winboard.c
13416         if(which) { // black clock
13417           if (gameMode == EditPosition || gameMode == IcsExamining) {
13418             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13419             SetBlackToPlayEvent();
13420           } else if (gameMode == EditGame || shiftKey) {
13421             AdjustClock(which, -1);
13422           } else if (gameMode == IcsPlayingWhite ||
13423                      gameMode == MachinePlaysBlack) {
13424             CallFlagEvent();
13425           }
13426         } else { // white clock
13427           if (gameMode == EditPosition || gameMode == IcsExamining) {
13428             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13429             SetWhiteToPlayEvent();
13430           } else if (gameMode == EditGame || shiftKey) {
13431             AdjustClock(which, -1);
13432           } else if (gameMode == IcsPlayingBlack ||
13433                    gameMode == MachinePlaysWhite) {
13434             CallFlagEvent();
13435           }
13436         }
13437 }
13438
13439 void
13440 DrawEvent()
13441 {
13442     /* Offer draw or accept pending draw offer from opponent */
13443
13444     if (appData.icsActive) {
13445         /* Note: tournament rules require draw offers to be
13446            made after you make your move but before you punch
13447            your clock.  Currently ICS doesn't let you do that;
13448            instead, you immediately punch your clock after making
13449            a move, but you can offer a draw at any time. */
13450
13451         SendToICS(ics_prefix);
13452         SendToICS("draw\n");
13453         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13454     } else if (cmailMsgLoaded) {
13455         if (currentMove == cmailOldMove &&
13456             commentList[cmailOldMove] != NULL &&
13457             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13458                    "Black offers a draw" : "White offers a draw")) {
13459             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13460             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13461         } else if (currentMove == cmailOldMove + 1) {
13462             char *offer = WhiteOnMove(cmailOldMove) ?
13463               "White offers a draw" : "Black offers a draw";
13464             AppendComment(currentMove, offer, TRUE);
13465             DisplayComment(currentMove - 1, offer);
13466             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13467         } else {
13468             DisplayError(_("You must make your move before offering a draw"), 0);
13469             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13470         }
13471     } else if (first.offeredDraw) {
13472         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13473     } else {
13474         if (first.sendDrawOffers) {
13475             SendToProgram("draw\n", &first);
13476             userOfferedDraw = TRUE;
13477         }
13478     }
13479 }
13480
13481 void
13482 AdjournEvent()
13483 {
13484     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13485
13486     if (appData.icsActive) {
13487         SendToICS(ics_prefix);
13488         SendToICS("adjourn\n");
13489     } else {
13490         /* Currently GNU Chess doesn't offer or accept Adjourns */
13491     }
13492 }
13493
13494
13495 void
13496 AbortEvent()
13497 {
13498     /* Offer Abort or accept pending Abort offer from opponent */
13499
13500     if (appData.icsActive) {
13501         SendToICS(ics_prefix);
13502         SendToICS("abort\n");
13503     } else {
13504         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13505     }
13506 }
13507
13508 void
13509 ResignEvent()
13510 {
13511     /* Resign.  You can do this even if it's not your turn. */
13512
13513     if (appData.icsActive) {
13514         SendToICS(ics_prefix);
13515         SendToICS("resign\n");
13516     } else {
13517         switch (gameMode) {
13518           case MachinePlaysWhite:
13519             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13520             break;
13521           case MachinePlaysBlack:
13522             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13523             break;
13524           case EditGame:
13525             if (cmailMsgLoaded) {
13526                 TruncateGame();
13527                 if (WhiteOnMove(cmailOldMove)) {
13528                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13529                 } else {
13530                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13531                 }
13532                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13533             }
13534             break;
13535           default:
13536             break;
13537         }
13538     }
13539 }
13540
13541
13542 void
13543 StopObservingEvent()
13544 {
13545     /* Stop observing current games */
13546     SendToICS(ics_prefix);
13547     SendToICS("unobserve\n");
13548 }
13549
13550 void
13551 StopExaminingEvent()
13552 {
13553     /* Stop observing current game */
13554     SendToICS(ics_prefix);
13555     SendToICS("unexamine\n");
13556 }
13557
13558 void
13559 ForwardInner(target)
13560      int target;
13561 {
13562     int limit;
13563
13564     if (appData.debugMode)
13565         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13566                 target, currentMove, forwardMostMove);
13567
13568     if (gameMode == EditPosition)
13569       return;
13570
13571     if (gameMode == PlayFromGameFile && !pausing)
13572       PauseEvent();
13573
13574     if (gameMode == IcsExamining && pausing)
13575       limit = pauseExamForwardMostMove;
13576     else
13577       limit = forwardMostMove;
13578
13579     if (target > limit) target = limit;
13580
13581     if (target > 0 && moveList[target - 1][0]) {
13582         int fromX, fromY, toX, toY;
13583         toX = moveList[target - 1][2] - AAA;
13584         toY = moveList[target - 1][3] - ONE;
13585         if (moveList[target - 1][1] == '@') {
13586             if (appData.highlightLastMove) {
13587                 SetHighlights(-1, -1, toX, toY);
13588             }
13589         } else {
13590             fromX = moveList[target - 1][0] - AAA;
13591             fromY = moveList[target - 1][1] - ONE;
13592             if (target == currentMove + 1) {
13593                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13594             }
13595             if (appData.highlightLastMove) {
13596                 SetHighlights(fromX, fromY, toX, toY);
13597             }
13598         }
13599     }
13600     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13601         gameMode == Training || gameMode == PlayFromGameFile ||
13602         gameMode == AnalyzeFile) {
13603         while (currentMove < target) {
13604             SendMoveToProgram(currentMove++, &first);
13605         }
13606     } else {
13607         currentMove = target;
13608     }
13609
13610     if (gameMode == EditGame || gameMode == EndOfGame) {
13611         whiteTimeRemaining = timeRemaining[0][currentMove];
13612         blackTimeRemaining = timeRemaining[1][currentMove];
13613     }
13614     DisplayBothClocks();
13615     DisplayMove(currentMove - 1);
13616     DrawPosition(FALSE, boards[currentMove]);
13617     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13618     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13619         DisplayComment(currentMove - 1, commentList[currentMove]);
13620     }
13621     DisplayBook(currentMove);
13622 }
13623
13624
13625 void
13626 ForwardEvent()
13627 {
13628     if (gameMode == IcsExamining && !pausing) {
13629         SendToICS(ics_prefix);
13630         SendToICS("forward\n");
13631     } else {
13632         ForwardInner(currentMove + 1);
13633     }
13634 }
13635
13636 void
13637 ToEndEvent()
13638 {
13639     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13640         /* to optimze, we temporarily turn off analysis mode while we feed
13641          * the remaining moves to the engine. Otherwise we get analysis output
13642          * after each move.
13643          */
13644         if (first.analysisSupport) {
13645           SendToProgram("exit\nforce\n", &first);
13646           first.analyzing = FALSE;
13647         }
13648     }
13649
13650     if (gameMode == IcsExamining && !pausing) {
13651         SendToICS(ics_prefix);
13652         SendToICS("forward 999999\n");
13653     } else {
13654         ForwardInner(forwardMostMove);
13655     }
13656
13657     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13658         /* we have fed all the moves, so reactivate analysis mode */
13659         SendToProgram("analyze\n", &first);
13660         first.analyzing = TRUE;
13661         /*first.maybeThinking = TRUE;*/
13662         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13663     }
13664 }
13665
13666 void
13667 BackwardInner(target)
13668      int target;
13669 {
13670     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13671
13672     if (appData.debugMode)
13673         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13674                 target, currentMove, forwardMostMove);
13675
13676     if (gameMode == EditPosition) return;
13677     if (currentMove <= backwardMostMove) {
13678         ClearHighlights();
13679         DrawPosition(full_redraw, boards[currentMove]);
13680         return;
13681     }
13682     if (gameMode == PlayFromGameFile && !pausing)
13683       PauseEvent();
13684
13685     if (moveList[target][0]) {
13686         int fromX, fromY, toX, toY;
13687         toX = moveList[target][2] - AAA;
13688         toY = moveList[target][3] - ONE;
13689         if (moveList[target][1] == '@') {
13690             if (appData.highlightLastMove) {
13691                 SetHighlights(-1, -1, toX, toY);
13692             }
13693         } else {
13694             fromX = moveList[target][0] - AAA;
13695             fromY = moveList[target][1] - ONE;
13696             if (target == currentMove - 1) {
13697                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13698             }
13699             if (appData.highlightLastMove) {
13700                 SetHighlights(fromX, fromY, toX, toY);
13701             }
13702         }
13703     }
13704     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13705         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13706         while (currentMove > target) {
13707             SendToProgram("undo\n", &first);
13708             currentMove--;
13709         }
13710     } else {
13711         currentMove = target;
13712     }
13713
13714     if (gameMode == EditGame || gameMode == EndOfGame) {
13715         whiteTimeRemaining = timeRemaining[0][currentMove];
13716         blackTimeRemaining = timeRemaining[1][currentMove];
13717     }
13718     DisplayBothClocks();
13719     DisplayMove(currentMove - 1);
13720     DrawPosition(full_redraw, boards[currentMove]);
13721     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13722     // [HGM] PV info: routine tests if comment empty
13723     DisplayComment(currentMove - 1, commentList[currentMove]);
13724     DisplayBook(currentMove);
13725 }
13726
13727 void
13728 BackwardEvent()
13729 {
13730     if (gameMode == IcsExamining && !pausing) {
13731         SendToICS(ics_prefix);
13732         SendToICS("backward\n");
13733     } else {
13734         BackwardInner(currentMove - 1);
13735     }
13736 }
13737
13738 void
13739 ToStartEvent()
13740 {
13741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13742         /* to optimize, we temporarily turn off analysis mode while we undo
13743          * all the moves. Otherwise we get analysis output after each undo.
13744          */
13745         if (first.analysisSupport) {
13746           SendToProgram("exit\nforce\n", &first);
13747           first.analyzing = FALSE;
13748         }
13749     }
13750
13751     if (gameMode == IcsExamining && !pausing) {
13752         SendToICS(ics_prefix);
13753         SendToICS("backward 999999\n");
13754     } else {
13755         BackwardInner(backwardMostMove);
13756     }
13757
13758     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13759         /* we have fed all the moves, so reactivate analysis mode */
13760         SendToProgram("analyze\n", &first);
13761         first.analyzing = TRUE;
13762         /*first.maybeThinking = TRUE;*/
13763         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13764     }
13765 }
13766
13767 void
13768 ToNrEvent(int to)
13769 {
13770   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13771   if (to >= forwardMostMove) to = forwardMostMove;
13772   if (to <= backwardMostMove) to = backwardMostMove;
13773   if (to < currentMove) {
13774     BackwardInner(to);
13775   } else {
13776     ForwardInner(to);
13777   }
13778 }
13779
13780 void
13781 RevertEvent(Boolean annotate)
13782 {
13783     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13784         return;
13785     }
13786     if (gameMode != IcsExamining) {
13787         DisplayError(_("You are not examining a game"), 0);
13788         return;
13789     }
13790     if (pausing) {
13791         DisplayError(_("You can't revert while pausing"), 0);
13792         return;
13793     }
13794     SendToICS(ics_prefix);
13795     SendToICS("revert\n");
13796 }
13797
13798 void
13799 RetractMoveEvent()
13800 {
13801     switch (gameMode) {
13802       case MachinePlaysWhite:
13803       case MachinePlaysBlack:
13804         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13805             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13806             return;
13807         }
13808         if (forwardMostMove < 2) return;
13809         currentMove = forwardMostMove = forwardMostMove - 2;
13810         whiteTimeRemaining = timeRemaining[0][currentMove];
13811         blackTimeRemaining = timeRemaining[1][currentMove];
13812         DisplayBothClocks();
13813         DisplayMove(currentMove - 1);
13814         ClearHighlights();/*!! could figure this out*/
13815         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13816         SendToProgram("remove\n", &first);
13817         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13818         break;
13819
13820       case BeginningOfGame:
13821       default:
13822         break;
13823
13824       case IcsPlayingWhite:
13825       case IcsPlayingBlack:
13826         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13827             SendToICS(ics_prefix);
13828             SendToICS("takeback 2\n");
13829         } else {
13830             SendToICS(ics_prefix);
13831             SendToICS("takeback 1\n");
13832         }
13833         break;
13834     }
13835 }
13836
13837 void
13838 MoveNowEvent()
13839 {
13840     ChessProgramState *cps;
13841
13842     switch (gameMode) {
13843       case MachinePlaysWhite:
13844         if (!WhiteOnMove(forwardMostMove)) {
13845             DisplayError(_("It is your turn"), 0);
13846             return;
13847         }
13848         cps = &first;
13849         break;
13850       case MachinePlaysBlack:
13851         if (WhiteOnMove(forwardMostMove)) {
13852             DisplayError(_("It is your turn"), 0);
13853             return;
13854         }
13855         cps = &first;
13856         break;
13857       case TwoMachinesPlay:
13858         if (WhiteOnMove(forwardMostMove) ==
13859             (first.twoMachinesColor[0] == 'w')) {
13860             cps = &first;
13861         } else {
13862             cps = &second;
13863         }
13864         break;
13865       case BeginningOfGame:
13866       default:
13867         return;
13868     }
13869     SendToProgram("?\n", cps);
13870 }
13871
13872 void
13873 TruncateGameEvent()
13874 {
13875     EditGameEvent();
13876     if (gameMode != EditGame) return;
13877     TruncateGame();
13878 }
13879
13880 void
13881 TruncateGame()
13882 {
13883     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13884     if (forwardMostMove > currentMove) {
13885         if (gameInfo.resultDetails != NULL) {
13886             free(gameInfo.resultDetails);
13887             gameInfo.resultDetails = NULL;
13888             gameInfo.result = GameUnfinished;
13889         }
13890         forwardMostMove = currentMove;
13891         HistorySet(parseList, backwardMostMove, forwardMostMove,
13892                    currentMove-1);
13893     }
13894 }
13895
13896 void
13897 HintEvent()
13898 {
13899     if (appData.noChessProgram) return;
13900     switch (gameMode) {
13901       case MachinePlaysWhite:
13902         if (WhiteOnMove(forwardMostMove)) {
13903             DisplayError(_("Wait until your turn"), 0);
13904             return;
13905         }
13906         break;
13907       case BeginningOfGame:
13908       case MachinePlaysBlack:
13909         if (!WhiteOnMove(forwardMostMove)) {
13910             DisplayError(_("Wait until your turn"), 0);
13911             return;
13912         }
13913         break;
13914       default:
13915         DisplayError(_("No hint available"), 0);
13916         return;
13917     }
13918     SendToProgram("hint\n", &first);
13919     hintRequested = TRUE;
13920 }
13921
13922 void
13923 BookEvent()
13924 {
13925     if (appData.noChessProgram) return;
13926     switch (gameMode) {
13927       case MachinePlaysWhite:
13928         if (WhiteOnMove(forwardMostMove)) {
13929             DisplayError(_("Wait until your turn"), 0);
13930             return;
13931         }
13932         break;
13933       case BeginningOfGame:
13934       case MachinePlaysBlack:
13935         if (!WhiteOnMove(forwardMostMove)) {
13936             DisplayError(_("Wait until your turn"), 0);
13937             return;
13938         }
13939         break;
13940       case EditPosition:
13941         EditPositionDone(TRUE);
13942         break;
13943       case TwoMachinesPlay:
13944         return;
13945       default:
13946         break;
13947     }
13948     SendToProgram("bk\n", &first);
13949     bookOutput[0] = NULLCHAR;
13950     bookRequested = TRUE;
13951 }
13952
13953 void
13954 AboutGameEvent()
13955 {
13956     char *tags = PGNTags(&gameInfo);
13957     TagsPopUp(tags, CmailMsg());
13958     free(tags);
13959 }
13960
13961 /* end button procedures */
13962
13963 void
13964 PrintPosition(fp, move)
13965      FILE *fp;
13966      int move;
13967 {
13968     int i, j;
13969
13970     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13971         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13972             char c = PieceToChar(boards[move][i][j]);
13973             fputc(c == 'x' ? '.' : c, fp);
13974             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13975         }
13976     }
13977     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13978       fprintf(fp, "white to play\n");
13979     else
13980       fprintf(fp, "black to play\n");
13981 }
13982
13983 void
13984 PrintOpponents(fp)
13985      FILE *fp;
13986 {
13987     if (gameInfo.white != NULL) {
13988         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13989     } else {
13990         fprintf(fp, "\n");
13991     }
13992 }
13993
13994 /* Find last component of program's own name, using some heuristics */
13995 void
13996 TidyProgramName(prog, host, buf)
13997      char *prog, *host, buf[MSG_SIZ];
13998 {
13999     char *p, *q;
14000     int local = (strcmp(host, "localhost") == 0);
14001     while (!local && (p = strchr(prog, ';')) != NULL) {
14002         p++;
14003         while (*p == ' ') p++;
14004         prog = p;
14005     }
14006     if (*prog == '"' || *prog == '\'') {
14007         q = strchr(prog + 1, *prog);
14008     } else {
14009         q = strchr(prog, ' ');
14010     }
14011     if (q == NULL) q = prog + strlen(prog);
14012     p = q;
14013     while (p >= prog && *p != '/' && *p != '\\') p--;
14014     p++;
14015     if(p == prog && *p == '"') p++;
14016     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14017     memcpy(buf, p, q - p);
14018     buf[q - p] = NULLCHAR;
14019     if (!local) {
14020         strcat(buf, "@");
14021         strcat(buf, host);
14022     }
14023 }
14024
14025 char *
14026 TimeControlTagValue()
14027 {
14028     char buf[MSG_SIZ];
14029     if (!appData.clockMode) {
14030       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14031     } else if (movesPerSession > 0) {
14032       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14033     } else if (timeIncrement == 0) {
14034       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14035     } else {
14036       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14037     }
14038     return StrSave(buf);
14039 }
14040
14041 void
14042 SetGameInfo()
14043 {
14044     /* This routine is used only for certain modes */
14045     VariantClass v = gameInfo.variant;
14046     ChessMove r = GameUnfinished;
14047     char *p = NULL;
14048
14049     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14050         r = gameInfo.result;
14051         p = gameInfo.resultDetails;
14052         gameInfo.resultDetails = NULL;
14053     }
14054     ClearGameInfo(&gameInfo);
14055     gameInfo.variant = v;
14056
14057     switch (gameMode) {
14058       case MachinePlaysWhite:
14059         gameInfo.event = StrSave( appData.pgnEventHeader );
14060         gameInfo.site = StrSave(HostName());
14061         gameInfo.date = PGNDate();
14062         gameInfo.round = StrSave("-");
14063         gameInfo.white = StrSave(first.tidy);
14064         gameInfo.black = StrSave(UserName());
14065         gameInfo.timeControl = TimeControlTagValue();
14066         break;
14067
14068       case MachinePlaysBlack:
14069         gameInfo.event = StrSave( appData.pgnEventHeader );
14070         gameInfo.site = StrSave(HostName());
14071         gameInfo.date = PGNDate();
14072         gameInfo.round = StrSave("-");
14073         gameInfo.white = StrSave(UserName());
14074         gameInfo.black = StrSave(first.tidy);
14075         gameInfo.timeControl = TimeControlTagValue();
14076         break;
14077
14078       case TwoMachinesPlay:
14079         gameInfo.event = StrSave( appData.pgnEventHeader );
14080         gameInfo.site = StrSave(HostName());
14081         gameInfo.date = PGNDate();
14082         if (roundNr > 0) {
14083             char buf[MSG_SIZ];
14084             snprintf(buf, MSG_SIZ, "%d", roundNr);
14085             gameInfo.round = StrSave(buf);
14086         } else {
14087             gameInfo.round = StrSave("-");
14088         }
14089         if (first.twoMachinesColor[0] == 'w') {
14090             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14091             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14092         } else {
14093             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14094             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14095         }
14096         gameInfo.timeControl = TimeControlTagValue();
14097         break;
14098
14099       case EditGame:
14100         gameInfo.event = StrSave("Edited game");
14101         gameInfo.site = StrSave(HostName());
14102         gameInfo.date = PGNDate();
14103         gameInfo.round = StrSave("-");
14104         gameInfo.white = StrSave("-");
14105         gameInfo.black = StrSave("-");
14106         gameInfo.result = r;
14107         gameInfo.resultDetails = p;
14108         break;
14109
14110       case EditPosition:
14111         gameInfo.event = StrSave("Edited position");
14112         gameInfo.site = StrSave(HostName());
14113         gameInfo.date = PGNDate();
14114         gameInfo.round = StrSave("-");
14115         gameInfo.white = StrSave("-");
14116         gameInfo.black = StrSave("-");
14117         break;
14118
14119       case IcsPlayingWhite:
14120       case IcsPlayingBlack:
14121       case IcsObserving:
14122       case IcsExamining:
14123         break;
14124
14125       case PlayFromGameFile:
14126         gameInfo.event = StrSave("Game from non-PGN file");
14127         gameInfo.site = StrSave(HostName());
14128         gameInfo.date = PGNDate();
14129         gameInfo.round = StrSave("-");
14130         gameInfo.white = StrSave("?");
14131         gameInfo.black = StrSave("?");
14132         break;
14133
14134       default:
14135         break;
14136     }
14137 }
14138
14139 void
14140 ReplaceComment(index, text)
14141      int index;
14142      char *text;
14143 {
14144     int len;
14145     char *p;
14146     float score;
14147
14148     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14149        pvInfoList[index-1].depth == len &&
14150        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14151        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14152     while (*text == '\n') text++;
14153     len = strlen(text);
14154     while (len > 0 && text[len - 1] == '\n') len--;
14155
14156     if (commentList[index] != NULL)
14157       free(commentList[index]);
14158
14159     if (len == 0) {
14160         commentList[index] = NULL;
14161         return;
14162     }
14163   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14164       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14165       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14166     commentList[index] = (char *) malloc(len + 2);
14167     strncpy(commentList[index], text, len);
14168     commentList[index][len] = '\n';
14169     commentList[index][len + 1] = NULLCHAR;
14170   } else {
14171     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14172     char *p;
14173     commentList[index] = (char *) malloc(len + 7);
14174     safeStrCpy(commentList[index], "{\n", 3);
14175     safeStrCpy(commentList[index]+2, text, len+1);
14176     commentList[index][len+2] = NULLCHAR;
14177     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14178     strcat(commentList[index], "\n}\n");
14179   }
14180 }
14181
14182 void
14183 CrushCRs(text)
14184      char *text;
14185 {
14186   char *p = text;
14187   char *q = text;
14188   char ch;
14189
14190   do {
14191     ch = *p++;
14192     if (ch == '\r') continue;
14193     *q++ = ch;
14194   } while (ch != '\0');
14195 }
14196
14197 void
14198 AppendComment(index, text, addBraces)
14199      int index;
14200      char *text;
14201      Boolean addBraces; // [HGM] braces: tells if we should add {}
14202 {
14203     int oldlen, len;
14204     char *old;
14205
14206 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14207     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14208
14209     CrushCRs(text);
14210     while (*text == '\n') text++;
14211     len = strlen(text);
14212     while (len > 0 && text[len - 1] == '\n') len--;
14213
14214     if (len == 0) return;
14215
14216     if (commentList[index] != NULL) {
14217         old = commentList[index];
14218         oldlen = strlen(old);
14219         while(commentList[index][oldlen-1] ==  '\n')
14220           commentList[index][--oldlen] = NULLCHAR;
14221         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14222         safeStrCpy(commentList[index], old, oldlen + len + 6);
14223         free(old);
14224         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14225         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14226           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14227           while (*text == '\n') { text++; len--; }
14228           commentList[index][--oldlen] = NULLCHAR;
14229       }
14230         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14231         else          strcat(commentList[index], "\n");
14232         strcat(commentList[index], text);
14233         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14234         else          strcat(commentList[index], "\n");
14235     } else {
14236         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14237         if(addBraces)
14238           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14239         else commentList[index][0] = NULLCHAR;
14240         strcat(commentList[index], text);
14241         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14242         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14243     }
14244 }
14245
14246 static char * FindStr( char * text, char * sub_text )
14247 {
14248     char * result = strstr( text, sub_text );
14249
14250     if( result != NULL ) {
14251         result += strlen( sub_text );
14252     }
14253
14254     return result;
14255 }
14256
14257 /* [AS] Try to extract PV info from PGN comment */
14258 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14259 char *GetInfoFromComment( int index, char * text )
14260 {
14261     char * sep = text, *p;
14262
14263     if( text != NULL && index > 0 ) {
14264         int score = 0;
14265         int depth = 0;
14266         int time = -1, sec = 0, deci;
14267         char * s_eval = FindStr( text, "[%eval " );
14268         char * s_emt = FindStr( text, "[%emt " );
14269
14270         if( s_eval != NULL || s_emt != NULL ) {
14271             /* New style */
14272             char delim;
14273
14274             if( s_eval != NULL ) {
14275                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14276                     return text;
14277                 }
14278
14279                 if( delim != ']' ) {
14280                     return text;
14281                 }
14282             }
14283
14284             if( s_emt != NULL ) {
14285             }
14286                 return text;
14287         }
14288         else {
14289             /* We expect something like: [+|-]nnn.nn/dd */
14290             int score_lo = 0;
14291
14292             if(*text != '{') return text; // [HGM] braces: must be normal comment
14293
14294             sep = strchr( text, '/' );
14295             if( sep == NULL || sep < (text+4) ) {
14296                 return text;
14297             }
14298
14299             p = text;
14300             if(p[1] == '(') { // comment starts with PV
14301                p = strchr(p, ')'); // locate end of PV
14302                if(p == NULL || sep < p+5) return text;
14303                // at this point we have something like "{(.*) +0.23/6 ..."
14304                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14305                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14306                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14307             }
14308             time = -1; sec = -1; deci = -1;
14309             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14310                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14311                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14312                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14313                 return text;
14314             }
14315
14316             if( score_lo < 0 || score_lo >= 100 ) {
14317                 return text;
14318             }
14319
14320             if(sec >= 0) time = 600*time + 10*sec; else
14321             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14322
14323             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14324
14325             /* [HGM] PV time: now locate end of PV info */
14326             while( *++sep >= '0' && *sep <= '9'); // strip depth
14327             if(time >= 0)
14328             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14329             if(sec >= 0)
14330             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14331             if(deci >= 0)
14332             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14333             while(*sep == ' ') sep++;
14334         }
14335
14336         if( depth <= 0 ) {
14337             return text;
14338         }
14339
14340         if( time < 0 ) {
14341             time = -1;
14342         }
14343
14344         pvInfoList[index-1].depth = depth;
14345         pvInfoList[index-1].score = score;
14346         pvInfoList[index-1].time  = 10*time; // centi-sec
14347         if(*sep == '}') *sep = 0; else *--sep = '{';
14348         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14349     }
14350     return sep;
14351 }
14352
14353 void
14354 SendToProgram(message, cps)
14355      char *message;
14356      ChessProgramState *cps;
14357 {
14358     int count, outCount, error;
14359     char buf[MSG_SIZ];
14360
14361     if (cps->pr == NULL) return;
14362     Attention(cps);
14363
14364     if (appData.debugMode) {
14365         TimeMark now;
14366         GetTimeMark(&now);
14367         fprintf(debugFP, "%ld >%-6s: %s",
14368                 SubtractTimeMarks(&now, &programStartTime),
14369                 cps->which, message);
14370     }
14371
14372     count = strlen(message);
14373     outCount = OutputToProcess(cps->pr, message, count, &error);
14374     if (outCount < count && !exiting
14375                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14376       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14377       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14378         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14379             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14380                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14381                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14382                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14383             } else {
14384                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14385                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14386                 gameInfo.result = res;
14387             }
14388             gameInfo.resultDetails = StrSave(buf);
14389         }
14390         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14391         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14392     }
14393 }
14394
14395 void
14396 ReceiveFromProgram(isr, closure, message, count, error)
14397      InputSourceRef isr;
14398      VOIDSTAR closure;
14399      char *message;
14400      int count;
14401      int error;
14402 {
14403     char *end_str;
14404     char buf[MSG_SIZ];
14405     ChessProgramState *cps = (ChessProgramState *)closure;
14406
14407     if (isr != cps->isr) return; /* Killed intentionally */
14408     if (count <= 0) {
14409         if (count == 0) {
14410             RemoveInputSource(cps->isr);
14411             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14412             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14413                     _(cps->which), cps->program);
14414         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14415                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14416                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14417                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14418                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14419                 } else {
14420                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14421                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14422                     gameInfo.result = res;
14423                 }
14424                 gameInfo.resultDetails = StrSave(buf);
14425             }
14426             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14427             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14428         } else {
14429             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14430                     _(cps->which), cps->program);
14431             RemoveInputSource(cps->isr);
14432
14433             /* [AS] Program is misbehaving badly... kill it */
14434             if( count == -2 ) {
14435                 DestroyChildProcess( cps->pr, 9 );
14436                 cps->pr = NoProc;
14437             }
14438
14439             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14440         }
14441         return;
14442     }
14443
14444     if ((end_str = strchr(message, '\r')) != NULL)
14445       *end_str = NULLCHAR;
14446     if ((end_str = strchr(message, '\n')) != NULL)
14447       *end_str = NULLCHAR;
14448
14449     if (appData.debugMode) {
14450         TimeMark now; int print = 1;
14451         char *quote = ""; char c; int i;
14452
14453         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14454                 char start = message[0];
14455                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14456                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14457                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14458                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14459                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14460                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14461                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14462                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14463                    sscanf(message, "hint: %c", &c)!=1 && 
14464                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14465                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14466                     print = (appData.engineComments >= 2);
14467                 }
14468                 message[0] = start; // restore original message
14469         }
14470         if(print) {
14471                 GetTimeMark(&now);
14472                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14473                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14474                         quote,
14475                         message);
14476         }
14477     }
14478
14479     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14480     if (appData.icsEngineAnalyze) {
14481         if (strstr(message, "whisper") != NULL ||
14482              strstr(message, "kibitz") != NULL ||
14483             strstr(message, "tellics") != NULL) return;
14484     }
14485
14486     HandleMachineMove(message, cps);
14487 }
14488
14489
14490 void
14491 SendTimeControl(cps, mps, tc, inc, sd, st)
14492      ChessProgramState *cps;
14493      int mps, inc, sd, st;
14494      long tc;
14495 {
14496     char buf[MSG_SIZ];
14497     int seconds;
14498
14499     if( timeControl_2 > 0 ) {
14500         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14501             tc = timeControl_2;
14502         }
14503     }
14504     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14505     inc /= cps->timeOdds;
14506     st  /= cps->timeOdds;
14507
14508     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14509
14510     if (st > 0) {
14511       /* Set exact time per move, normally using st command */
14512       if (cps->stKludge) {
14513         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14514         seconds = st % 60;
14515         if (seconds == 0) {
14516           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14517         } else {
14518           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14519         }
14520       } else {
14521         snprintf(buf, MSG_SIZ, "st %d\n", st);
14522       }
14523     } else {
14524       /* Set conventional or incremental time control, using level command */
14525       if (seconds == 0) {
14526         /* Note old gnuchess bug -- minutes:seconds used to not work.
14527            Fixed in later versions, but still avoid :seconds
14528            when seconds is 0. */
14529         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14530       } else {
14531         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14532                  seconds, inc/1000.);
14533       }
14534     }
14535     SendToProgram(buf, cps);
14536
14537     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14538     /* Orthogonally, limit search to given depth */
14539     if (sd > 0) {
14540       if (cps->sdKludge) {
14541         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14542       } else {
14543         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14544       }
14545       SendToProgram(buf, cps);
14546     }
14547
14548     if(cps->nps >= 0) { /* [HGM] nps */
14549         if(cps->supportsNPS == FALSE)
14550           cps->nps = -1; // don't use if engine explicitly says not supported!
14551         else {
14552           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14553           SendToProgram(buf, cps);
14554         }
14555     }
14556 }
14557
14558 ChessProgramState *WhitePlayer()
14559 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14560 {
14561     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14562        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14563         return &second;
14564     return &first;
14565 }
14566
14567 void
14568 SendTimeRemaining(cps, machineWhite)
14569      ChessProgramState *cps;
14570      int /*boolean*/ machineWhite;
14571 {
14572     char message[MSG_SIZ];
14573     long time, otime;
14574
14575     /* Note: this routine must be called when the clocks are stopped
14576        or when they have *just* been set or switched; otherwise
14577        it will be off by the time since the current tick started.
14578     */
14579     if (machineWhite) {
14580         time = whiteTimeRemaining / 10;
14581         otime = blackTimeRemaining / 10;
14582     } else {
14583         time = blackTimeRemaining / 10;
14584         otime = whiteTimeRemaining / 10;
14585     }
14586     /* [HGM] translate opponent's time by time-odds factor */
14587     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14588     if (appData.debugMode) {
14589         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14590     }
14591
14592     if (time <= 0) time = 1;
14593     if (otime <= 0) otime = 1;
14594
14595     snprintf(message, MSG_SIZ, "time %ld\n", time);
14596     SendToProgram(message, cps);
14597
14598     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14599     SendToProgram(message, cps);
14600 }
14601
14602 int
14603 BoolFeature(p, name, loc, cps)
14604      char **p;
14605      char *name;
14606      int *loc;
14607      ChessProgramState *cps;
14608 {
14609   char buf[MSG_SIZ];
14610   int len = strlen(name);
14611   int val;
14612
14613   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14614     (*p) += len + 1;
14615     sscanf(*p, "%d", &val);
14616     *loc = (val != 0);
14617     while (**p && **p != ' ')
14618       (*p)++;
14619     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14620     SendToProgram(buf, cps);
14621     return TRUE;
14622   }
14623   return FALSE;
14624 }
14625
14626 int
14627 IntFeature(p, name, loc, cps)
14628      char **p;
14629      char *name;
14630      int *loc;
14631      ChessProgramState *cps;
14632 {
14633   char buf[MSG_SIZ];
14634   int len = strlen(name);
14635   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14636     (*p) += len + 1;
14637     sscanf(*p, "%d", loc);
14638     while (**p && **p != ' ') (*p)++;
14639     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14640     SendToProgram(buf, cps);
14641     return TRUE;
14642   }
14643   return FALSE;
14644 }
14645
14646 int
14647 StringFeature(p, name, loc, cps)
14648      char **p;
14649      char *name;
14650      char loc[];
14651      ChessProgramState *cps;
14652 {
14653   char buf[MSG_SIZ];
14654   int len = strlen(name);
14655   if (strncmp((*p), name, len) == 0
14656       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14657     (*p) += len + 2;
14658     sscanf(*p, "%[^\"]", loc);
14659     while (**p && **p != '\"') (*p)++;
14660     if (**p == '\"') (*p)++;
14661     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14662     SendToProgram(buf, cps);
14663     return TRUE;
14664   }
14665   return FALSE;
14666 }
14667
14668 int
14669 ParseOption(Option *opt, ChessProgramState *cps)
14670 // [HGM] options: process the string that defines an engine option, and determine
14671 // name, type, default value, and allowed value range
14672 {
14673         char *p, *q, buf[MSG_SIZ];
14674         int n, min = (-1)<<31, max = 1<<31, def;
14675
14676         if(p = strstr(opt->name, " -spin ")) {
14677             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14678             if(max < min) max = min; // enforce consistency
14679             if(def < min) def = min;
14680             if(def > max) def = max;
14681             opt->value = def;
14682             opt->min = min;
14683             opt->max = max;
14684             opt->type = Spin;
14685         } else if((p = strstr(opt->name, " -slider "))) {
14686             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14687             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14688             if(max < min) max = min; // enforce consistency
14689             if(def < min) def = min;
14690             if(def > max) def = max;
14691             opt->value = def;
14692             opt->min = min;
14693             opt->max = max;
14694             opt->type = Spin; // Slider;
14695         } else if((p = strstr(opt->name, " -string "))) {
14696             opt->textValue = p+9;
14697             opt->type = TextBox;
14698         } else if((p = strstr(opt->name, " -file "))) {
14699             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14700             opt->textValue = p+7;
14701             opt->type = FileName; // FileName;
14702         } else if((p = strstr(opt->name, " -path "))) {
14703             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14704             opt->textValue = p+7;
14705             opt->type = PathName; // PathName;
14706         } else if(p = strstr(opt->name, " -check ")) {
14707             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14708             opt->value = (def != 0);
14709             opt->type = CheckBox;
14710         } else if(p = strstr(opt->name, " -combo ")) {
14711             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14712             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14713             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14714             opt->value = n = 0;
14715             while(q = StrStr(q, " /// ")) {
14716                 n++; *q = 0;    // count choices, and null-terminate each of them
14717                 q += 5;
14718                 if(*q == '*') { // remember default, which is marked with * prefix
14719                     q++;
14720                     opt->value = n;
14721                 }
14722                 cps->comboList[cps->comboCnt++] = q;
14723             }
14724             cps->comboList[cps->comboCnt++] = NULL;
14725             opt->max = n + 1;
14726             opt->type = ComboBox;
14727         } else if(p = strstr(opt->name, " -button")) {
14728             opt->type = Button;
14729         } else if(p = strstr(opt->name, " -save")) {
14730             opt->type = SaveButton;
14731         } else return FALSE;
14732         *p = 0; // terminate option name
14733         // now look if the command-line options define a setting for this engine option.
14734         if(cps->optionSettings && cps->optionSettings[0])
14735             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14736         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14737           snprintf(buf, MSG_SIZ, "option %s", p);
14738                 if(p = strstr(buf, ",")) *p = 0;
14739                 if(q = strchr(buf, '=')) switch(opt->type) {
14740                     case ComboBox:
14741                         for(n=0; n<opt->max; n++)
14742                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14743                         break;
14744                     case TextBox:
14745                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14746                         break;
14747                     case Spin:
14748                     case CheckBox:
14749                         opt->value = atoi(q+1);
14750                     default:
14751                         break;
14752                 }
14753                 strcat(buf, "\n");
14754                 SendToProgram(buf, cps);
14755         }
14756         return TRUE;
14757 }
14758
14759 void
14760 FeatureDone(cps, val)
14761      ChessProgramState* cps;
14762      int val;
14763 {
14764   DelayedEventCallback cb = GetDelayedEvent();
14765   if ((cb == InitBackEnd3 && cps == &first) ||
14766       (cb == SettingsMenuIfReady && cps == &second) ||
14767       (cb == LoadEngine) ||
14768       (cb == TwoMachinesEventIfReady)) {
14769     CancelDelayedEvent();
14770     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14771   }
14772   cps->initDone = val;
14773 }
14774
14775 /* Parse feature command from engine */
14776 void
14777 ParseFeatures(args, cps)
14778      char* args;
14779      ChessProgramState *cps;
14780 {
14781   char *p = args;
14782   char *q;
14783   int val;
14784   char buf[MSG_SIZ];
14785
14786   for (;;) {
14787     while (*p == ' ') p++;
14788     if (*p == NULLCHAR) return;
14789
14790     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14791     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14792     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14793     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14794     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14795     if (BoolFeature(&p, "reuse", &val, cps)) {
14796       /* Engine can disable reuse, but can't enable it if user said no */
14797       if (!val) cps->reuse = FALSE;
14798       continue;
14799     }
14800     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14801     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14802       if (gameMode == TwoMachinesPlay) {
14803         DisplayTwoMachinesTitle();
14804       } else {
14805         DisplayTitle("");
14806       }
14807       continue;
14808     }
14809     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14810     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14811     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14812     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14813     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14814     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14815     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14816     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14817     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14818     if (IntFeature(&p, "done", &val, cps)) {
14819       FeatureDone(cps, val);
14820       continue;
14821     }
14822     /* Added by Tord: */
14823     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14824     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14825     /* End of additions by Tord */
14826
14827     /* [HGM] added features: */
14828     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14829     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14830     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14831     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14832     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14833     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14834     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14835         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14836           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14837             SendToProgram(buf, cps);
14838             continue;
14839         }
14840         if(cps->nrOptions >= MAX_OPTIONS) {
14841             cps->nrOptions--;
14842             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14843             DisplayError(buf, 0);
14844         }
14845         continue;
14846     }
14847     /* End of additions by HGM */
14848
14849     /* unknown feature: complain and skip */
14850     q = p;
14851     while (*q && *q != '=') q++;
14852     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14853     SendToProgram(buf, cps);
14854     p = q;
14855     if (*p == '=') {
14856       p++;
14857       if (*p == '\"') {
14858         p++;
14859         while (*p && *p != '\"') p++;
14860         if (*p == '\"') p++;
14861       } else {
14862         while (*p && *p != ' ') p++;
14863       }
14864     }
14865   }
14866
14867 }
14868
14869 void
14870 PeriodicUpdatesEvent(newState)
14871      int newState;
14872 {
14873     if (newState == appData.periodicUpdates)
14874       return;
14875
14876     appData.periodicUpdates=newState;
14877
14878     /* Display type changes, so update it now */
14879 //    DisplayAnalysis();
14880
14881     /* Get the ball rolling again... */
14882     if (newState) {
14883         AnalysisPeriodicEvent(1);
14884         StartAnalysisClock();
14885     }
14886 }
14887
14888 void
14889 PonderNextMoveEvent(newState)
14890      int newState;
14891 {
14892     if (newState == appData.ponderNextMove) return;
14893     if (gameMode == EditPosition) EditPositionDone(TRUE);
14894     if (newState) {
14895         SendToProgram("hard\n", &first);
14896         if (gameMode == TwoMachinesPlay) {
14897             SendToProgram("hard\n", &second);
14898         }
14899     } else {
14900         SendToProgram("easy\n", &first);
14901         thinkOutput[0] = NULLCHAR;
14902         if (gameMode == TwoMachinesPlay) {
14903             SendToProgram("easy\n", &second);
14904         }
14905     }
14906     appData.ponderNextMove = newState;
14907 }
14908
14909 void
14910 NewSettingEvent(option, feature, command, value)
14911      char *command;
14912      int option, value, *feature;
14913 {
14914     char buf[MSG_SIZ];
14915
14916     if (gameMode == EditPosition) EditPositionDone(TRUE);
14917     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14918     if(feature == NULL || *feature) SendToProgram(buf, &first);
14919     if (gameMode == TwoMachinesPlay) {
14920         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14921     }
14922 }
14923
14924 void
14925 ShowThinkingEvent()
14926 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14927 {
14928     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14929     int newState = appData.showThinking
14930         // [HGM] thinking: other features now need thinking output as well
14931         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14932
14933     if (oldState == newState) return;
14934     oldState = newState;
14935     if (gameMode == EditPosition) EditPositionDone(TRUE);
14936     if (oldState) {
14937         SendToProgram("post\n", &first);
14938         if (gameMode == TwoMachinesPlay) {
14939             SendToProgram("post\n", &second);
14940         }
14941     } else {
14942         SendToProgram("nopost\n", &first);
14943         thinkOutput[0] = NULLCHAR;
14944         if (gameMode == TwoMachinesPlay) {
14945             SendToProgram("nopost\n", &second);
14946         }
14947     }
14948 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14949 }
14950
14951 void
14952 AskQuestionEvent(title, question, replyPrefix, which)
14953      char *title; char *question; char *replyPrefix; char *which;
14954 {
14955   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14956   if (pr == NoProc) return;
14957   AskQuestion(title, question, replyPrefix, pr);
14958 }
14959
14960 void
14961 TypeInEvent(char firstChar)
14962 {
14963     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14964         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14965         gameMode == AnalyzeMode || gameMode == EditGame || \r
14966         gameMode == EditPosition || gameMode == IcsExamining ||\r
14967         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14968         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14969                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14970                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14971         gameMode == Training) PopUpMoveDialog(firstChar);
14972 }
14973
14974 void
14975 TypeInDoneEvent(char *move)
14976 {
14977         Board board;
14978         int n, fromX, fromY, toX, toY;
14979         char promoChar;
14980         ChessMove moveType;\r
14981
14982         // [HGM] FENedit\r
14983         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14984                 EditPositionPasteFEN(move);\r
14985                 return;\r
14986         }\r
14987         // [HGM] movenum: allow move number to be typed in any mode\r
14988         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14989           ToNrEvent(2*n-1);\r
14990           return;\r
14991         }\r
14992
14993       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14994         gameMode != Training) {\r
14995         DisplayMoveError(_("Displayed move is not current"));\r
14996       } else {\r
14997         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14998           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14999         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15000         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15001           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15002           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15003         } else {\r
15004           DisplayMoveError(_("Could not parse move"));\r
15005         }
15006       }\r
15007 }\r
15008
15009 void
15010 DisplayMove(moveNumber)
15011      int moveNumber;
15012 {
15013     char message[MSG_SIZ];
15014     char res[MSG_SIZ];
15015     char cpThinkOutput[MSG_SIZ];
15016
15017     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15018
15019     if (moveNumber == forwardMostMove - 1 ||
15020         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15021
15022         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15023
15024         if (strchr(cpThinkOutput, '\n')) {
15025             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15026         }
15027     } else {
15028         *cpThinkOutput = NULLCHAR;
15029     }
15030
15031     /* [AS] Hide thinking from human user */
15032     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15033         *cpThinkOutput = NULLCHAR;
15034         if( thinkOutput[0] != NULLCHAR ) {
15035             int i;
15036
15037             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15038                 cpThinkOutput[i] = '.';
15039             }
15040             cpThinkOutput[i] = NULLCHAR;
15041             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15042         }
15043     }
15044
15045     if (moveNumber == forwardMostMove - 1 &&
15046         gameInfo.resultDetails != NULL) {
15047         if (gameInfo.resultDetails[0] == NULLCHAR) {
15048           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15049         } else {
15050           snprintf(res, MSG_SIZ, " {%s} %s",
15051                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15052         }
15053     } else {
15054         res[0] = NULLCHAR;
15055     }
15056
15057     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15058         DisplayMessage(res, cpThinkOutput);
15059     } else {
15060       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15061                 WhiteOnMove(moveNumber) ? " " : ".. ",
15062                 parseList[moveNumber], res);
15063         DisplayMessage(message, cpThinkOutput);
15064     }
15065 }
15066
15067 void
15068 DisplayComment(moveNumber, text)
15069      int moveNumber;
15070      char *text;
15071 {
15072     char title[MSG_SIZ];
15073     char buf[8000]; // comment can be long!
15074     int score, depth;
15075
15076     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15077       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15078     } else {
15079       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15080               WhiteOnMove(moveNumber) ? " " : ".. ",
15081               parseList[moveNumber]);
15082     }
15083     // [HGM] PV info: display PV info together with (or as) comment
15084     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15085       if(text == NULL) text = "";
15086       score = pvInfoList[moveNumber].score;
15087       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15088               depth, (pvInfoList[moveNumber].time+50)/100, text);
15089       text = buf;
15090     }
15091     if (text != NULL && (appData.autoDisplayComment || commentUp))
15092         CommentPopUp(title, text);
15093 }
15094
15095 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15096  * might be busy thinking or pondering.  It can be omitted if your
15097  * gnuchess is configured to stop thinking immediately on any user
15098  * input.  However, that gnuchess feature depends on the FIONREAD
15099  * ioctl, which does not work properly on some flavors of Unix.
15100  */
15101 void
15102 Attention(cps)
15103      ChessProgramState *cps;
15104 {
15105 #if ATTENTION
15106     if (!cps->useSigint) return;
15107     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15108     switch (gameMode) {
15109       case MachinePlaysWhite:
15110       case MachinePlaysBlack:
15111       case TwoMachinesPlay:
15112       case IcsPlayingWhite:
15113       case IcsPlayingBlack:
15114       case AnalyzeMode:
15115       case AnalyzeFile:
15116         /* Skip if we know it isn't thinking */
15117         if (!cps->maybeThinking) return;
15118         if (appData.debugMode)
15119           fprintf(debugFP, "Interrupting %s\n", cps->which);
15120         InterruptChildProcess(cps->pr);
15121         cps->maybeThinking = FALSE;
15122         break;
15123       default:
15124         break;
15125     }
15126 #endif /*ATTENTION*/
15127 }
15128
15129 int
15130 CheckFlags()
15131 {
15132     if (whiteTimeRemaining <= 0) {
15133         if (!whiteFlag) {
15134             whiteFlag = TRUE;
15135             if (appData.icsActive) {
15136                 if (appData.autoCallFlag &&
15137                     gameMode == IcsPlayingBlack && !blackFlag) {
15138                   SendToICS(ics_prefix);
15139                   SendToICS("flag\n");
15140                 }
15141             } else {
15142                 if (blackFlag) {
15143                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15144                 } else {
15145                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15146                     if (appData.autoCallFlag) {
15147                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15148                         return TRUE;
15149                     }
15150                 }
15151             }
15152         }
15153     }
15154     if (blackTimeRemaining <= 0) {
15155         if (!blackFlag) {
15156             blackFlag = TRUE;
15157             if (appData.icsActive) {
15158                 if (appData.autoCallFlag &&
15159                     gameMode == IcsPlayingWhite && !whiteFlag) {
15160                   SendToICS(ics_prefix);
15161                   SendToICS("flag\n");
15162                 }
15163             } else {
15164                 if (whiteFlag) {
15165                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15166                 } else {
15167                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15168                     if (appData.autoCallFlag) {
15169                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15170                         return TRUE;
15171                     }
15172                 }
15173             }
15174         }
15175     }
15176     return FALSE;
15177 }
15178
15179 void
15180 CheckTimeControl()
15181 {
15182     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15183         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15184
15185     /*
15186      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15187      */
15188     if ( !WhiteOnMove(forwardMostMove) ) {
15189         /* White made time control */
15190         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15191         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15192         /* [HGM] time odds: correct new time quota for time odds! */
15193                                             / WhitePlayer()->timeOdds;
15194         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15195     } else {
15196         lastBlack -= blackTimeRemaining;
15197         /* Black made time control */
15198         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15199                                             / WhitePlayer()->other->timeOdds;
15200         lastWhite = whiteTimeRemaining;
15201     }
15202 }
15203
15204 void
15205 DisplayBothClocks()
15206 {
15207     int wom = gameMode == EditPosition ?
15208       !blackPlaysFirst : WhiteOnMove(currentMove);
15209     DisplayWhiteClock(whiteTimeRemaining, wom);
15210     DisplayBlackClock(blackTimeRemaining, !wom);
15211 }
15212
15213
15214 /* Timekeeping seems to be a portability nightmare.  I think everyone
15215    has ftime(), but I'm really not sure, so I'm including some ifdefs
15216    to use other calls if you don't.  Clocks will be less accurate if
15217    you have neither ftime nor gettimeofday.
15218 */
15219
15220 /* VS 2008 requires the #include outside of the function */
15221 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15222 #include <sys/timeb.h>
15223 #endif
15224
15225 /* Get the current time as a TimeMark */
15226 void
15227 GetTimeMark(tm)
15228      TimeMark *tm;
15229 {
15230 #if HAVE_GETTIMEOFDAY
15231
15232     struct timeval timeVal;
15233     struct timezone timeZone;
15234
15235     gettimeofday(&timeVal, &timeZone);
15236     tm->sec = (long) timeVal.tv_sec;
15237     tm->ms = (int) (timeVal.tv_usec / 1000L);
15238
15239 #else /*!HAVE_GETTIMEOFDAY*/
15240 #if HAVE_FTIME
15241
15242 // include <sys/timeb.h> / moved to just above start of function
15243     struct timeb timeB;
15244
15245     ftime(&timeB);
15246     tm->sec = (long) timeB.time;
15247     tm->ms = (int) timeB.millitm;
15248
15249 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15250     tm->sec = (long) time(NULL);
15251     tm->ms = 0;
15252 #endif
15253 #endif
15254 }
15255
15256 /* Return the difference in milliseconds between two
15257    time marks.  We assume the difference will fit in a long!
15258 */
15259 long
15260 SubtractTimeMarks(tm2, tm1)
15261      TimeMark *tm2, *tm1;
15262 {
15263     return 1000L*(tm2->sec - tm1->sec) +
15264            (long) (tm2->ms - tm1->ms);
15265 }
15266
15267
15268 /*
15269  * Code to manage the game clocks.
15270  *
15271  * In tournament play, black starts the clock and then white makes a move.
15272  * We give the human user a slight advantage if he is playing white---the
15273  * clocks don't run until he makes his first move, so it takes zero time.
15274  * Also, we don't account for network lag, so we could get out of sync
15275  * with GNU Chess's clock -- but then, referees are always right.
15276  */
15277
15278 static TimeMark tickStartTM;
15279 static long intendedTickLength;
15280
15281 long
15282 NextTickLength(timeRemaining)
15283      long timeRemaining;
15284 {
15285     long nominalTickLength, nextTickLength;
15286
15287     if (timeRemaining > 0L && timeRemaining <= 10000L)
15288       nominalTickLength = 100L;
15289     else
15290       nominalTickLength = 1000L;
15291     nextTickLength = timeRemaining % nominalTickLength;
15292     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15293
15294     return nextTickLength;
15295 }
15296
15297 /* Adjust clock one minute up or down */
15298 void
15299 AdjustClock(Boolean which, int dir)
15300 {
15301     if(which) blackTimeRemaining += 60000*dir;
15302     else      whiteTimeRemaining += 60000*dir;
15303     DisplayBothClocks();
15304 }
15305
15306 /* Stop clocks and reset to a fresh time control */
15307 void
15308 ResetClocks()
15309 {
15310     (void) StopClockTimer();
15311     if (appData.icsActive) {
15312         whiteTimeRemaining = blackTimeRemaining = 0;
15313     } else if (searchTime) {
15314         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15315         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15316     } else { /* [HGM] correct new time quote for time odds */
15317         whiteTC = blackTC = fullTimeControlString;
15318         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15319         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15320     }
15321     if (whiteFlag || blackFlag) {
15322         DisplayTitle("");
15323         whiteFlag = blackFlag = FALSE;
15324     }
15325     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15326     DisplayBothClocks();
15327 }
15328
15329 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15330
15331 /* Decrement running clock by amount of time that has passed */
15332 void
15333 DecrementClocks()
15334 {
15335     long timeRemaining;
15336     long lastTickLength, fudge;
15337     TimeMark now;
15338
15339     if (!appData.clockMode) return;
15340     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15341
15342     GetTimeMark(&now);
15343
15344     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15345
15346     /* Fudge if we woke up a little too soon */
15347     fudge = intendedTickLength - lastTickLength;
15348     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15349
15350     if (WhiteOnMove(forwardMostMove)) {
15351         if(whiteNPS >= 0) lastTickLength = 0;
15352         timeRemaining = whiteTimeRemaining -= lastTickLength;
15353         if(timeRemaining < 0 && !appData.icsActive) {
15354             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15355             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15356                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15357                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15358             }
15359         }
15360         DisplayWhiteClock(whiteTimeRemaining - fudge,
15361                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15362     } else {
15363         if(blackNPS >= 0) lastTickLength = 0;
15364         timeRemaining = blackTimeRemaining -= lastTickLength;
15365         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15366             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15367             if(suddenDeath) {
15368                 blackStartMove = forwardMostMove;
15369                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15370             }
15371         }
15372         DisplayBlackClock(blackTimeRemaining - fudge,
15373                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15374     }
15375     if (CheckFlags()) return;
15376
15377     tickStartTM = now;
15378     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15379     StartClockTimer(intendedTickLength);
15380
15381     /* if the time remaining has fallen below the alarm threshold, sound the
15382      * alarm. if the alarm has sounded and (due to a takeback or time control
15383      * with increment) the time remaining has increased to a level above the
15384      * threshold, reset the alarm so it can sound again.
15385      */
15386
15387     if (appData.icsActive && appData.icsAlarm) {
15388
15389         /* make sure we are dealing with the user's clock */
15390         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15391                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15392            )) return;
15393
15394         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15395             alarmSounded = FALSE;
15396         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15397             PlayAlarmSound();
15398             alarmSounded = TRUE;
15399         }
15400     }
15401 }
15402
15403
15404 /* A player has just moved, so stop the previously running
15405    clock and (if in clock mode) start the other one.
15406    We redisplay both clocks in case we're in ICS mode, because
15407    ICS gives us an update to both clocks after every move.
15408    Note that this routine is called *after* forwardMostMove
15409    is updated, so the last fractional tick must be subtracted
15410    from the color that is *not* on move now.
15411 */
15412 void
15413 SwitchClocks(int newMoveNr)
15414 {
15415     long lastTickLength;
15416     TimeMark now;
15417     int flagged = FALSE;
15418
15419     GetTimeMark(&now);
15420
15421     if (StopClockTimer() && appData.clockMode) {
15422         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15423         if (!WhiteOnMove(forwardMostMove)) {
15424             if(blackNPS >= 0) lastTickLength = 0;
15425             blackTimeRemaining -= lastTickLength;
15426            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15427 //         if(pvInfoList[forwardMostMove].time == -1)
15428                  pvInfoList[forwardMostMove].time =               // use GUI time
15429                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15430         } else {
15431            if(whiteNPS >= 0) lastTickLength = 0;
15432            whiteTimeRemaining -= lastTickLength;
15433            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15434 //         if(pvInfoList[forwardMostMove].time == -1)
15435                  pvInfoList[forwardMostMove].time =
15436                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15437         }
15438         flagged = CheckFlags();
15439     }
15440     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15441     CheckTimeControl();
15442
15443     if (flagged || !appData.clockMode) return;
15444
15445     switch (gameMode) {
15446       case MachinePlaysBlack:
15447       case MachinePlaysWhite:
15448       case BeginningOfGame:
15449         if (pausing) return;
15450         break;
15451
15452       case EditGame:
15453       case PlayFromGameFile:
15454       case IcsExamining:
15455         return;
15456
15457       default:
15458         break;
15459     }
15460
15461     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15462         if(WhiteOnMove(forwardMostMove))
15463              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15464         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15465     }
15466
15467     tickStartTM = now;
15468     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15469       whiteTimeRemaining : blackTimeRemaining);
15470     StartClockTimer(intendedTickLength);
15471 }
15472
15473
15474 /* Stop both clocks */
15475 void
15476 StopClocks()
15477 {
15478     long lastTickLength;
15479     TimeMark now;
15480
15481     if (!StopClockTimer()) return;
15482     if (!appData.clockMode) return;
15483
15484     GetTimeMark(&now);
15485
15486     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15487     if (WhiteOnMove(forwardMostMove)) {
15488         if(whiteNPS >= 0) lastTickLength = 0;
15489         whiteTimeRemaining -= lastTickLength;
15490         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15491     } else {
15492         if(blackNPS >= 0) lastTickLength = 0;
15493         blackTimeRemaining -= lastTickLength;
15494         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15495     }
15496     CheckFlags();
15497 }
15498
15499 /* Start clock of player on move.  Time may have been reset, so
15500    if clock is already running, stop and restart it. */
15501 void
15502 StartClocks()
15503 {
15504     (void) StopClockTimer(); /* in case it was running already */
15505     DisplayBothClocks();
15506     if (CheckFlags()) return;
15507
15508     if (!appData.clockMode) return;
15509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15510
15511     GetTimeMark(&tickStartTM);
15512     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15513       whiteTimeRemaining : blackTimeRemaining);
15514
15515    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15516     whiteNPS = blackNPS = -1;
15517     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15518        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15519         whiteNPS = first.nps;
15520     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15521        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15522         blackNPS = first.nps;
15523     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15524         whiteNPS = second.nps;
15525     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15526         blackNPS = second.nps;
15527     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15528
15529     StartClockTimer(intendedTickLength);
15530 }
15531
15532 char *
15533 TimeString(ms)
15534      long ms;
15535 {
15536     long second, minute, hour, day;
15537     char *sign = "";
15538     static char buf[32];
15539
15540     if (ms > 0 && ms <= 9900) {
15541       /* convert milliseconds to tenths, rounding up */
15542       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15543
15544       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15545       return buf;
15546     }
15547
15548     /* convert milliseconds to seconds, rounding up */
15549     /* use floating point to avoid strangeness of integer division
15550        with negative dividends on many machines */
15551     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15552
15553     if (second < 0) {
15554         sign = "-";
15555         second = -second;
15556     }
15557
15558     day = second / (60 * 60 * 24);
15559     second = second % (60 * 60 * 24);
15560     hour = second / (60 * 60);
15561     second = second % (60 * 60);
15562     minute = second / 60;
15563     second = second % 60;
15564
15565     if (day > 0)
15566       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15567               sign, day, hour, minute, second);
15568     else if (hour > 0)
15569       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15570     else
15571       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15572
15573     return buf;
15574 }
15575
15576
15577 /*
15578  * This is necessary because some C libraries aren't ANSI C compliant yet.
15579  */
15580 char *
15581 StrStr(string, match)
15582      char *string, *match;
15583 {
15584     int i, length;
15585
15586     length = strlen(match);
15587
15588     for (i = strlen(string) - length; i >= 0; i--, string++)
15589       if (!strncmp(match, string, length))
15590         return string;
15591
15592     return NULL;
15593 }
15594
15595 char *
15596 StrCaseStr(string, match)
15597      char *string, *match;
15598 {
15599     int i, j, length;
15600
15601     length = strlen(match);
15602
15603     for (i = strlen(string) - length; i >= 0; i--, string++) {
15604         for (j = 0; j < length; j++) {
15605             if (ToLower(match[j]) != ToLower(string[j]))
15606               break;
15607         }
15608         if (j == length) return string;
15609     }
15610
15611     return NULL;
15612 }
15613
15614 #ifndef _amigados
15615 int
15616 StrCaseCmp(s1, s2)
15617      char *s1, *s2;
15618 {
15619     char c1, c2;
15620
15621     for (;;) {
15622         c1 = ToLower(*s1++);
15623         c2 = ToLower(*s2++);
15624         if (c1 > c2) return 1;
15625         if (c1 < c2) return -1;
15626         if (c1 == NULLCHAR) return 0;
15627     }
15628 }
15629
15630
15631 int
15632 ToLower(c)
15633      int c;
15634 {
15635     return isupper(c) ? tolower(c) : c;
15636 }
15637
15638
15639 int
15640 ToUpper(c)
15641      int c;
15642 {
15643     return islower(c) ? toupper(c) : c;
15644 }
15645 #endif /* !_amigados    */
15646
15647 char *
15648 StrSave(s)
15649      char *s;
15650 {
15651   char *ret;
15652
15653   if ((ret = (char *) malloc(strlen(s) + 1)))
15654     {
15655       safeStrCpy(ret, s, strlen(s)+1);
15656     }
15657   return ret;
15658 }
15659
15660 char *
15661 StrSavePtr(s, savePtr)
15662      char *s, **savePtr;
15663 {
15664     if (*savePtr) {
15665         free(*savePtr);
15666     }
15667     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15668       safeStrCpy(*savePtr, s, strlen(s)+1);
15669     }
15670     return(*savePtr);
15671 }
15672
15673 char *
15674 PGNDate()
15675 {
15676     time_t clock;
15677     struct tm *tm;
15678     char buf[MSG_SIZ];
15679
15680     clock = time((time_t *)NULL);
15681     tm = localtime(&clock);
15682     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15683             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15684     return StrSave(buf);
15685 }
15686
15687
15688 char *
15689 PositionToFEN(move, overrideCastling)
15690      int move;
15691      char *overrideCastling;
15692 {
15693     int i, j, fromX, fromY, toX, toY;
15694     int whiteToPlay;
15695     char buf[128];
15696     char *p, *q;
15697     int emptycount;
15698     ChessSquare piece;
15699
15700     whiteToPlay = (gameMode == EditPosition) ?
15701       !blackPlaysFirst : (move % 2 == 0);
15702     p = buf;
15703
15704     /* Piece placement data */
15705     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15706         emptycount = 0;
15707         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15708             if (boards[move][i][j] == EmptySquare) {
15709                 emptycount++;
15710             } else { ChessSquare piece = boards[move][i][j];
15711                 if (emptycount > 0) {
15712                     if(emptycount<10) /* [HGM] can be >= 10 */
15713                         *p++ = '0' + emptycount;
15714                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15715                     emptycount = 0;
15716                 }
15717                 if(PieceToChar(piece) == '+') {
15718                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15719                     *p++ = '+';
15720                     piece = (ChessSquare)(DEMOTED piece);
15721                 }
15722                 *p++ = PieceToChar(piece);
15723                 if(p[-1] == '~') {
15724                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15725                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15726                     *p++ = '~';
15727                 }
15728             }
15729         }
15730         if (emptycount > 0) {
15731             if(emptycount<10) /* [HGM] can be >= 10 */
15732                 *p++ = '0' + emptycount;
15733             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15734             emptycount = 0;
15735         }
15736         *p++ = '/';
15737     }
15738     *(p - 1) = ' ';
15739
15740     /* [HGM] print Crazyhouse or Shogi holdings */
15741     if( gameInfo.holdingsWidth ) {
15742         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15743         q = p;
15744         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15745             piece = boards[move][i][BOARD_WIDTH-1];
15746             if( piece != EmptySquare )
15747               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15748                   *p++ = PieceToChar(piece);
15749         }
15750         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15751             piece = boards[move][BOARD_HEIGHT-i-1][0];
15752             if( piece != EmptySquare )
15753               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15754                   *p++ = PieceToChar(piece);
15755         }
15756
15757         if( q == p ) *p++ = '-';
15758         *p++ = ']';
15759         *p++ = ' ';
15760     }
15761
15762     /* Active color */
15763     *p++ = whiteToPlay ? 'w' : 'b';
15764     *p++ = ' ';
15765
15766   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15767     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15768   } else {
15769   if(nrCastlingRights) {
15770      q = p;
15771      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15772        /* [HGM] write directly from rights */
15773            if(boards[move][CASTLING][2] != NoRights &&
15774               boards[move][CASTLING][0] != NoRights   )
15775                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15776            if(boards[move][CASTLING][2] != NoRights &&
15777               boards[move][CASTLING][1] != NoRights   )
15778                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15779            if(boards[move][CASTLING][5] != NoRights &&
15780               boards[move][CASTLING][3] != NoRights   )
15781                 *p++ = boards[move][CASTLING][3] + AAA;
15782            if(boards[move][CASTLING][5] != NoRights &&
15783               boards[move][CASTLING][4] != NoRights   )
15784                 *p++ = boards[move][CASTLING][4] + AAA;
15785      } else {
15786
15787         /* [HGM] write true castling rights */
15788         if( nrCastlingRights == 6 ) {
15789             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15790                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15791             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15792                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15793             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15794                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15795             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15796                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15797         }
15798      }
15799      if (q == p) *p++ = '-'; /* No castling rights */
15800      *p++ = ' ';
15801   }
15802
15803   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15804      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15805     /* En passant target square */
15806     if (move > backwardMostMove) {
15807         fromX = moveList[move - 1][0] - AAA;
15808         fromY = moveList[move - 1][1] - ONE;
15809         toX = moveList[move - 1][2] - AAA;
15810         toY = moveList[move - 1][3] - ONE;
15811         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15812             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15813             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15814             fromX == toX) {
15815             /* 2-square pawn move just happened */
15816             *p++ = toX + AAA;
15817             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15818         } else {
15819             *p++ = '-';
15820         }
15821     } else if(move == backwardMostMove) {
15822         // [HGM] perhaps we should always do it like this, and forget the above?
15823         if((signed char)boards[move][EP_STATUS] >= 0) {
15824             *p++ = boards[move][EP_STATUS] + AAA;
15825             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15826         } else {
15827             *p++ = '-';
15828         }
15829     } else {
15830         *p++ = '-';
15831     }
15832     *p++ = ' ';
15833   }
15834   }
15835
15836     /* [HGM] find reversible plies */
15837     {   int i = 0, j=move;
15838
15839         if (appData.debugMode) { int k;
15840             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15841             for(k=backwardMostMove; k<=forwardMostMove; k++)
15842                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15843
15844         }
15845
15846         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15847         if( j == backwardMostMove ) i += initialRulePlies;
15848         sprintf(p, "%d ", i);
15849         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15850     }
15851     /* Fullmove number */
15852     sprintf(p, "%d", (move / 2) + 1);
15853
15854     return StrSave(buf);
15855 }
15856
15857 Boolean
15858 ParseFEN(board, blackPlaysFirst, fen)
15859     Board board;
15860      int *blackPlaysFirst;
15861      char *fen;
15862 {
15863     int i, j;
15864     char *p, c;
15865     int emptycount;
15866     ChessSquare piece;
15867
15868     p = fen;
15869
15870     /* [HGM] by default clear Crazyhouse holdings, if present */
15871     if(gameInfo.holdingsWidth) {
15872        for(i=0; i<BOARD_HEIGHT; i++) {
15873            board[i][0]             = EmptySquare; /* black holdings */
15874            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15875            board[i][1]             = (ChessSquare) 0; /* black counts */
15876            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15877        }
15878     }
15879
15880     /* Piece placement data */
15881     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15882         j = 0;
15883         for (;;) {
15884             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15885                 if (*p == '/') p++;
15886                 emptycount = gameInfo.boardWidth - j;
15887                 while (emptycount--)
15888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15889                 break;
15890 #if(BOARD_FILES >= 10)
15891             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15892                 p++; emptycount=10;
15893                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15894                 while (emptycount--)
15895                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15896 #endif
15897             } else if (isdigit(*p)) {
15898                 emptycount = *p++ - '0';
15899                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15900                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15901                 while (emptycount--)
15902                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15903             } else if (*p == '+' || isalpha(*p)) {
15904                 if (j >= gameInfo.boardWidth) return FALSE;
15905                 if(*p=='+') {
15906                     piece = CharToPiece(*++p);
15907                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15908                     piece = (ChessSquare) (PROMOTED piece ); p++;
15909                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15910                 } else piece = CharToPiece(*p++);
15911
15912                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15913                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15914                     piece = (ChessSquare) (PROMOTED piece);
15915                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15916                     p++;
15917                 }
15918                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15919             } else {
15920                 return FALSE;
15921             }
15922         }
15923     }
15924     while (*p == '/' || *p == ' ') p++;
15925
15926     /* [HGM] look for Crazyhouse holdings here */
15927     while(*p==' ') p++;
15928     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15929         if(*p == '[') p++;
15930         if(*p == '-' ) p++; /* empty holdings */ else {
15931             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15932             /* if we would allow FEN reading to set board size, we would   */
15933             /* have to add holdings and shift the board read so far here   */
15934             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15935                 p++;
15936                 if((int) piece >= (int) BlackPawn ) {
15937                     i = (int)piece - (int)BlackPawn;
15938                     i = PieceToNumber((ChessSquare)i);
15939                     if( i >= gameInfo.holdingsSize ) return FALSE;
15940                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15941                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15942                 } else {
15943                     i = (int)piece - (int)WhitePawn;
15944                     i = PieceToNumber((ChessSquare)i);
15945                     if( i >= gameInfo.holdingsSize ) return FALSE;
15946                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15947                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15948                 }
15949             }
15950         }
15951         if(*p == ']') p++;
15952     }
15953
15954     while(*p == ' ') p++;
15955
15956     /* Active color */
15957     c = *p++;
15958     if(appData.colorNickNames) {
15959       if( c == appData.colorNickNames[0] ) c = 'w'; else
15960       if( c == appData.colorNickNames[1] ) c = 'b';
15961     }
15962     switch (c) {
15963       case 'w':
15964         *blackPlaysFirst = FALSE;
15965         break;
15966       case 'b':
15967         *blackPlaysFirst = TRUE;
15968         break;
15969       default:
15970         return FALSE;
15971     }
15972
15973     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15974     /* return the extra info in global variiables             */
15975
15976     /* set defaults in case FEN is incomplete */
15977     board[EP_STATUS] = EP_UNKNOWN;
15978     for(i=0; i<nrCastlingRights; i++ ) {
15979         board[CASTLING][i] =
15980             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15981     }   /* assume possible unless obviously impossible */
15982     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15983     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15984     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15985                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15986     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15987     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15988     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15989                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15990     FENrulePlies = 0;
15991
15992     while(*p==' ') p++;
15993     if(nrCastlingRights) {
15994       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15995           /* castling indicator present, so default becomes no castlings */
15996           for(i=0; i<nrCastlingRights; i++ ) {
15997                  board[CASTLING][i] = NoRights;
15998           }
15999       }
16000       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16001              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16002              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16003              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16004         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16005
16006         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16007             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16008             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16009         }
16010         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16011             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16012         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16013                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16014         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16015                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16016         switch(c) {
16017           case'K':
16018               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16019               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16020               board[CASTLING][2] = whiteKingFile;
16021               break;
16022           case'Q':
16023               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16024               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16025               board[CASTLING][2] = whiteKingFile;
16026               break;
16027           case'k':
16028               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16029               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16030               board[CASTLING][5] = blackKingFile;
16031               break;
16032           case'q':
16033               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16034               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16035               board[CASTLING][5] = blackKingFile;
16036           case '-':
16037               break;
16038           default: /* FRC castlings */
16039               if(c >= 'a') { /* black rights */
16040                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16041                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16042                   if(i == BOARD_RGHT) break;
16043                   board[CASTLING][5] = i;
16044                   c -= AAA;
16045                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16046                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16047                   if(c > i)
16048                       board[CASTLING][3] = c;
16049                   else
16050                       board[CASTLING][4] = c;
16051               } else { /* white rights */
16052                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16053                     if(board[0][i] == WhiteKing) break;
16054                   if(i == BOARD_RGHT) break;
16055                   board[CASTLING][2] = i;
16056                   c -= AAA - 'a' + 'A';
16057                   if(board[0][c] >= WhiteKing) break;
16058                   if(c > i)
16059                       board[CASTLING][0] = c;
16060                   else
16061                       board[CASTLING][1] = c;
16062               }
16063         }
16064       }
16065       for(i=0; i<nrCastlingRights; i++)
16066         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16067     if (appData.debugMode) {
16068         fprintf(debugFP, "FEN castling rights:");
16069         for(i=0; i<nrCastlingRights; i++)
16070         fprintf(debugFP, " %d", board[CASTLING][i]);
16071         fprintf(debugFP, "\n");
16072     }
16073
16074       while(*p==' ') p++;
16075     }
16076
16077     /* read e.p. field in games that know e.p. capture */
16078     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16079        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16080       if(*p=='-') {
16081         p++; board[EP_STATUS] = EP_NONE;
16082       } else {
16083          char c = *p++ - AAA;
16084
16085          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16086          if(*p >= '0' && *p <='9') p++;
16087          board[EP_STATUS] = c;
16088       }
16089     }
16090
16091
16092     if(sscanf(p, "%d", &i) == 1) {
16093         FENrulePlies = i; /* 50-move ply counter */
16094         /* (The move number is still ignored)    */
16095     }
16096
16097     return TRUE;
16098 }
16099
16100 void
16101 EditPositionPasteFEN(char *fen)
16102 {
16103   if (fen != NULL) {
16104     Board initial_position;
16105
16106     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16107       DisplayError(_("Bad FEN position in clipboard"), 0);
16108       return ;
16109     } else {
16110       int savedBlackPlaysFirst = blackPlaysFirst;
16111       EditPositionEvent();
16112       blackPlaysFirst = savedBlackPlaysFirst;
16113       CopyBoard(boards[0], initial_position);
16114       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16115       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16116       DisplayBothClocks();
16117       DrawPosition(FALSE, boards[currentMove]);
16118     }
16119   }
16120 }
16121
16122 static char cseq[12] = "\\   ";
16123
16124 Boolean set_cont_sequence(char *new_seq)
16125 {
16126     int len;
16127     Boolean ret;
16128
16129     // handle bad attempts to set the sequence
16130         if (!new_seq)
16131                 return 0; // acceptable error - no debug
16132
16133     len = strlen(new_seq);
16134     ret = (len > 0) && (len < sizeof(cseq));
16135     if (ret)
16136       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16137     else if (appData.debugMode)
16138       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16139     return ret;
16140 }
16141
16142 /*
16143     reformat a source message so words don't cross the width boundary.  internal
16144     newlines are not removed.  returns the wrapped size (no null character unless
16145     included in source message).  If dest is NULL, only calculate the size required
16146     for the dest buffer.  lp argument indicats line position upon entry, and it's
16147     passed back upon exit.
16148 */
16149 int wrap(char *dest, char *src, int count, int width, int *lp)
16150 {
16151     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16152
16153     cseq_len = strlen(cseq);
16154     old_line = line = *lp;
16155     ansi = len = clen = 0;
16156
16157     for (i=0; i < count; i++)
16158     {
16159         if (src[i] == '\033')
16160             ansi = 1;
16161
16162         // if we hit the width, back up
16163         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16164         {
16165             // store i & len in case the word is too long
16166             old_i = i, old_len = len;
16167
16168             // find the end of the last word
16169             while (i && src[i] != ' ' && src[i] != '\n')
16170             {
16171                 i--;
16172                 len--;
16173             }
16174
16175             // word too long?  restore i & len before splitting it
16176             if ((old_i-i+clen) >= width)
16177             {
16178                 i = old_i;
16179                 len = old_len;
16180             }
16181
16182             // extra space?
16183             if (i && src[i-1] == ' ')
16184                 len--;
16185
16186             if (src[i] != ' ' && src[i] != '\n')
16187             {
16188                 i--;
16189                 if (len)
16190                     len--;
16191             }
16192
16193             // now append the newline and continuation sequence
16194             if (dest)
16195                 dest[len] = '\n';
16196             len++;
16197             if (dest)
16198                 strncpy(dest+len, cseq, cseq_len);
16199             len += cseq_len;
16200             line = cseq_len;
16201             clen = cseq_len;
16202             continue;
16203         }
16204
16205         if (dest)
16206             dest[len] = src[i];
16207         len++;
16208         if (!ansi)
16209             line++;
16210         if (src[i] == '\n')
16211             line = 0;
16212         if (src[i] == 'm')
16213             ansi = 0;
16214     }
16215     if (dest && appData.debugMode)
16216     {
16217         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16218             count, width, line, len, *lp);
16219         show_bytes(debugFP, src, count);
16220         fprintf(debugFP, "\ndest: ");
16221         show_bytes(debugFP, dest, len);
16222         fprintf(debugFP, "\n");
16223     }
16224     *lp = dest ? line : old_line;
16225
16226     return len;
16227 }
16228
16229 // [HGM] vari: routines for shelving variations
16230
16231 void
16232 PushInner(int firstMove, int lastMove)
16233 {
16234         int i, j, nrMoves = lastMove - firstMove;
16235
16236         // push current tail of game on stack
16237         savedResult[storedGames] = gameInfo.result;
16238         savedDetails[storedGames] = gameInfo.resultDetails;
16239         gameInfo.resultDetails = NULL;
16240         savedFirst[storedGames] = firstMove;
16241         savedLast [storedGames] = lastMove;
16242         savedFramePtr[storedGames] = framePtr;
16243         framePtr -= nrMoves; // reserve space for the boards
16244         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16245             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16246             for(j=0; j<MOVE_LEN; j++)
16247                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16248             for(j=0; j<2*MOVE_LEN; j++)
16249                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16250             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16251             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16252             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16253             pvInfoList[firstMove+i-1].depth = 0;
16254             commentList[framePtr+i] = commentList[firstMove+i];
16255             commentList[firstMove+i] = NULL;
16256         }
16257
16258         storedGames++;
16259         forwardMostMove = firstMove; // truncate game so we can start variation
16260 }
16261
16262 void
16263 PushTail(int firstMove, int lastMove)
16264 {
16265         if(appData.icsActive) { // only in local mode
16266                 forwardMostMove = currentMove; // mimic old ICS behavior
16267                 return;
16268         }
16269         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16270
16271         PushInner(firstMove, lastMove);
16272         if(storedGames == 1) GreyRevert(FALSE);
16273 }
16274
16275 void
16276 PopInner(Boolean annotate)
16277 {
16278         int i, j, nrMoves;
16279         char buf[8000], moveBuf[20];
16280
16281         storedGames--;
16282         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16283         nrMoves = savedLast[storedGames] - currentMove;
16284         if(annotate) {
16285                 int cnt = 10;
16286                 if(!WhiteOnMove(currentMove))
16287                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16288                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16289                 for(i=currentMove; i<forwardMostMove; i++) {
16290                         if(WhiteOnMove(i))
16291                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16292                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16293                         strcat(buf, moveBuf);
16294                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16295                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16296                 }
16297                 strcat(buf, ")");
16298         }
16299         for(i=1; i<=nrMoves; i++) { // copy last variation back
16300             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16301             for(j=0; j<MOVE_LEN; j++)
16302                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16303             for(j=0; j<2*MOVE_LEN; j++)
16304                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16305             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16306             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16307             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16308             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16309             commentList[currentMove+i] = commentList[framePtr+i];
16310             commentList[framePtr+i] = NULL;
16311         }
16312         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16313         framePtr = savedFramePtr[storedGames];
16314         gameInfo.result = savedResult[storedGames];
16315         if(gameInfo.resultDetails != NULL) {
16316             free(gameInfo.resultDetails);
16317       }
16318         gameInfo.resultDetails = savedDetails[storedGames];
16319         forwardMostMove = currentMove + nrMoves;
16320 }
16321
16322 Boolean
16323 PopTail(Boolean annotate)
16324 {
16325         if(appData.icsActive) return FALSE; // only in local mode
16326         if(!storedGames) return FALSE; // sanity
16327         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16328
16329         PopInner(annotate);
16330
16331         if(storedGames == 0) GreyRevert(TRUE);
16332         return TRUE;
16333 }
16334
16335 void
16336 CleanupTail()
16337 {       // remove all shelved variations
16338         int i;
16339         for(i=0; i<storedGames; i++) {
16340             if(savedDetails[i])
16341                 free(savedDetails[i]);
16342             savedDetails[i] = NULL;
16343         }
16344         for(i=framePtr; i<MAX_MOVES; i++) {
16345                 if(commentList[i]) free(commentList[i]);
16346                 commentList[i] = NULL;
16347         }
16348         framePtr = MAX_MOVES-1;
16349         storedGames = 0;
16350 }
16351
16352 void
16353 LoadVariation(int index, char *text)
16354 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16355         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16356         int level = 0, move;
16357
16358         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16359         // first find outermost bracketing variation
16360         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16361             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16362                 if(*p == '{') wait = '}'; else
16363                 if(*p == '[') wait = ']'; else
16364                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16365                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16366             }
16367             if(*p == wait) wait = NULLCHAR; // closing ]} found
16368             p++;
16369         }
16370         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16371         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16372         end[1] = NULLCHAR; // clip off comment beyond variation
16373         ToNrEvent(currentMove-1);
16374         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16375         // kludge: use ParsePV() to append variation to game
16376         move = currentMove;
16377         ParsePV(start, TRUE, TRUE);
16378         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16379         ClearPremoveHighlights();
16380         CommentPopDown();
16381         ToNrEvent(currentMove+1);
16382 }
16383