8d6a27398602c7c2e463fda24817384edf8d336a
[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 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174                    /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186                            char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188                         int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 int ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
195
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228 void NextMatchGame P((void));
229 int NextTourneyGame P((int nr, int *swap));
230 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
231 FILE *WriteTourneyFile P((char *results, FILE *f));
232 void DisplayTwoMachinesTitle P(());
233
234 #ifdef WIN32
235        extern void ConsoleCreate();
236 #endif
237
238 ChessProgramState *WhitePlayer();
239 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
240 int VerifyDisplayMode P(());
241
242 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
243 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
244 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
245 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
246 void ics_update_width P((int new_width));
247 extern char installDir[MSG_SIZ];
248 VariantClass startVariant; /* [HGM] nicks: initial variant */
249 Boolean abortMatch;
250
251 extern int tinyLayout, smallLayout;
252 ChessProgramStats programStats;
253 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
254 int endPV = -1;
255 static int exiting = 0; /* [HGM] moved to top */
256 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
257 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
258 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
259 int partnerHighlight[2];
260 Boolean partnerBoardValid = 0;
261 char partnerStatus[MSG_SIZ];
262 Boolean partnerUp;
263 Boolean originalFlip;
264 Boolean twoBoards = 0;
265 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
266 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
267 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
268 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
269 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
270 int opponentKibitzes;
271 int lastSavedGame; /* [HGM] save: ID of game */
272 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
273 extern int chatCount;
274 int chattingPartner;
275 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
276 char lastMsg[MSG_SIZ];
277 ChessSquare pieceSweep = EmptySquare;
278 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
279 int promoDefaultAltered;
280
281 /* States for ics_getting_history */
282 #define H_FALSE 0
283 #define H_REQUESTED 1
284 #define H_GOT_REQ_HEADER 2
285 #define H_GOT_UNREQ_HEADER 3
286 #define H_GETTING_MOVES 4
287 #define H_GOT_UNWANTED_HEADER 5
288
289 /* whosays values for GameEnds */
290 #define GE_ICS 0
291 #define GE_ENGINE 1
292 #define GE_PLAYER 2
293 #define GE_FILE 3
294 #define GE_XBOARD 4
295 #define GE_ENGINE1 5
296 #define GE_ENGINE2 6
297
298 /* Maximum number of games in a cmail message */
299 #define CMAIL_MAX_GAMES 20
300
301 /* Different types of move when calling RegisterMove */
302 #define CMAIL_MOVE   0
303 #define CMAIL_RESIGN 1
304 #define CMAIL_DRAW   2
305 #define CMAIL_ACCEPT 3
306
307 /* Different types of result to remember for each game */
308 #define CMAIL_NOT_RESULT 0
309 #define CMAIL_OLD_RESULT 1
310 #define CMAIL_NEW_RESULT 2
311
312 /* Telnet protocol constants */
313 #define TN_WILL 0373
314 #define TN_WONT 0374
315 #define TN_DO   0375
316 #define TN_DONT 0376
317 #define TN_IAC  0377
318 #define TN_ECHO 0001
319 #define TN_SGA  0003
320 #define TN_PORT 23
321
322 char*
323 safeStrCpy( char *dst, const char *src, size_t count )
324 { // [HGM] made safe
325   int i;
326   assert( dst != NULL );
327   assert( src != NULL );
328   assert( count > 0 );
329
330   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
331   if(  i == count && dst[count-1] != NULLCHAR)
332     {
333       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
334       if(appData.debugMode)
335       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
336     }
337
338   return dst;
339 }
340
341 /* Some compiler can't cast u64 to double
342  * This function do the job for us:
343
344  * We use the highest bit for cast, this only
345  * works if the highest bit is not
346  * in use (This should not happen)
347  *
348  * We used this for all compiler
349  */
350 double
351 u64ToDouble(u64 value)
352 {
353   double r;
354   u64 tmp = value & u64Const(0x7fffffffffffffff);
355   r = (double)(s64)tmp;
356   if (value & u64Const(0x8000000000000000))
357        r +=  9.2233720368547758080e18; /* 2^63 */
358  return r;
359 }
360
361 /* Fake up flags for now, as we aren't keeping track of castling
362    availability yet. [HGM] Change of logic: the flag now only
363    indicates the type of castlings allowed by the rule of the game.
364    The actual rights themselves are maintained in the array
365    castlingRights, as part of the game history, and are not probed
366    by this function.
367  */
368 int
369 PosFlags(index)
370 {
371   int flags = F_ALL_CASTLE_OK;
372   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
373   switch (gameInfo.variant) {
374   case VariantSuicide:
375     flags &= ~F_ALL_CASTLE_OK;
376   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
377     flags |= F_IGNORE_CHECK;
378   case VariantLosers:
379     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380     break;
381   case VariantAtomic:
382     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
383     break;
384   case VariantKriegspiel:
385     flags |= F_KRIEGSPIEL_CAPTURE;
386     break;
387   case VariantCapaRandom:
388   case VariantFischeRandom:
389     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
390   case VariantNoCastle:
391   case VariantShatranj:
392   case VariantCourier:
393   case VariantMakruk:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP;
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 };
570
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 };
577
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 };
584
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 };
591
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
597 };
598
599 ChessSquare GrandArray[2][BOARD_FILES] = {
600     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
601         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
602     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
603         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
604 };
605
606 #ifdef GOTHIC
607 ChessSquare GothicArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
609         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
610     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
611         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
612 };
613 #else // !GOTHIC
614 #define GothicArray CapablancaArray
615 #endif // !GOTHIC
616
617 #ifdef FALCON
618 ChessSquare FalconArray[2][BOARD_FILES] = {
619     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
620         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
621     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
622         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
623 };
624 #else // !FALCON
625 #define FalconArray CapablancaArray
626 #endif // !FALCON
627
628 #else // !(BOARD_FILES>=10)
629 #define XiangqiPosition FIDEArray
630 #define CapablancaArray FIDEArray
631 #define GothicArray FIDEArray
632 #define GreatArray FIDEArray
633 #endif // !(BOARD_FILES>=10)
634
635 #if (BOARD_FILES>=12)
636 ChessSquare CourierArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
638         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
639     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
640         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
641 };
642 #else // !(BOARD_FILES>=12)
643 #define CourierArray CapablancaArray
644 #endif // !(BOARD_FILES>=12)
645
646
647 Board initialPosition;
648
649
650 /* Convert str to a rating. Checks for special cases of "----",
651
652    "++++", etc. Also strips ()'s */
653 int
654 string_to_rating(str)
655   char *str;
656 {
657   while(*str && !isdigit(*str)) ++str;
658   if (!*str)
659     return 0;   /* One of the special "no rating" cases */
660   else
661     return atoi(str);
662 }
663
664 void
665 ClearProgramStats()
666 {
667     /* Init programStats */
668     programStats.movelist[0] = 0;
669     programStats.depth = 0;
670     programStats.nr_moves = 0;
671     programStats.moves_left = 0;
672     programStats.nodes = 0;
673     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
674     programStats.score = 0;
675     programStats.got_only_move = 0;
676     programStats.got_fail = 0;
677     programStats.line_is_book = 0;
678 }
679
680 void
681 CommonEngineInit()
682 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
683     if (appData.firstPlaysBlack) {
684         first.twoMachinesColor = "black\n";
685         second.twoMachinesColor = "white\n";
686     } else {
687         first.twoMachinesColor = "white\n";
688         second.twoMachinesColor = "black\n";
689     }
690
691     first.other = &second;
692     second.other = &first;
693
694     { float norm = 1;
695         if(appData.timeOddsMode) {
696             norm = appData.timeOdds[0];
697             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
698         }
699         first.timeOdds  = appData.timeOdds[0]/norm;
700         second.timeOdds = appData.timeOdds[1]/norm;
701     }
702
703     if(programVersion) free(programVersion);
704     if (appData.noChessProgram) {
705         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
706         sprintf(programVersion, "%s", PACKAGE_STRING);
707     } else {
708       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
709       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
710       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711     }
712 }
713
714 void
715 UnloadEngine(ChessProgramState *cps)
716 {
717         /* Kill off first chess program */
718         if (cps->isr != NULL)
719           RemoveInputSource(cps->isr);
720         cps->isr = NULL;
721
722         if (cps->pr != NoProc) {
723             ExitAnalyzeMode();
724             DoSleep( appData.delayBeforeQuit );
725             SendToProgram("quit\n", cps);
726             DoSleep( appData.delayAfterQuit );
727             DestroyChildProcess(cps->pr, cps->useSigterm);
728         }
729         cps->pr = NoProc;
730         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
731 }
732
733 void
734 ClearOptions(ChessProgramState *cps)
735 {
736     int i;
737     cps->nrOptions = cps->comboCnt = 0;
738     for(i=0; i<MAX_OPTIONS; i++) {
739         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
740         cps->option[i].textValue = 0;
741     }
742 }
743
744 char *engineNames[] = {
745 "first",
746 "second"
747 };
748
749 void
750 InitEngine(ChessProgramState *cps, int n)
751 {   // [HGM] all engine initialiation put in a function that does one engine
752
753     ClearOptions(cps);
754
755     cps->which = engineNames[n];
756     cps->maybeThinking = FALSE;
757     cps->pr = NoProc;
758     cps->isr = NULL;
759     cps->sendTime = 2;
760     cps->sendDrawOffers = 1;
761
762     cps->program = appData.chessProgram[n];
763     cps->host = appData.host[n];
764     cps->dir = appData.directory[n];
765     cps->initString = appData.engInitString[n];
766     cps->computerString = appData.computerString[n];
767     cps->useSigint  = TRUE;
768     cps->useSigterm = TRUE;
769     cps->reuse = appData.reuse[n];
770     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
771     cps->useSetboard = FALSE;
772     cps->useSAN = FALSE;
773     cps->usePing = FALSE;
774     cps->lastPing = 0;
775     cps->lastPong = 0;
776     cps->usePlayother = FALSE;
777     cps->useColors = TRUE;
778     cps->useUsermove = FALSE;
779     cps->sendICS = FALSE;
780     cps->sendName = appData.icsActive;
781     cps->sdKludge = FALSE;
782     cps->stKludge = FALSE;
783     TidyProgramName(cps->program, cps->host, cps->tidy);
784     cps->matchWins = 0;
785     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786     cps->analysisSupport = 2; /* detect */
787     cps->analyzing = FALSE;
788     cps->initDone = FALSE;
789
790     /* New features added by Tord: */
791     cps->useFEN960 = FALSE;
792     cps->useOOCastle = TRUE;
793     /* End of new features added by Tord. */
794     cps->fenOverride  = appData.fenOverride[n];
795
796     /* [HGM] time odds: set factor for each machine */
797     cps->timeOdds  = appData.timeOdds[n];
798
799     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
800     cps->accumulateTC = appData.accumulateTC[n];
801     cps->maxNrOfSessions = 1;
802
803     /* [HGM] debug */
804     cps->debug = FALSE;
805
806     cps->supportsNPS = UNKNOWN;
807     cps->memSize = FALSE;
808     cps->maxCores = FALSE;
809     cps->egtFormats[0] = NULLCHAR;
810
811     /* [HGM] options */
812     cps->optionSettings  = appData.engOptions[n];
813
814     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
815     cps->isUCI = appData.isUCI[n]; /* [AS] */
816     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
817
818     if (appData.protocolVersion[n] > PROTOVER
819         || appData.protocolVersion[n] < 1)
820       {
821         char buf[MSG_SIZ];
822         int len;
823
824         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
825                        appData.protocolVersion[n]);
826         if( (len > MSG_SIZ) && appData.debugMode )
827           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
828
829         DisplayFatalError(buf, 0, 2);
830       }
831     else
832       {
833         cps->protocolVersion = appData.protocolVersion[n];
834       }
835
836     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
837     ParseFeatures(appData.featureDefaults, cps);
838 }
839
840 ChessProgramState *savCps;
841
842 void
843 LoadEngine()
844 {
845     int i;
846     if(WaitForEngine(savCps, LoadEngine)) return;
847     CommonEngineInit(); // recalculate time odds
848     if(gameInfo.variant != StringToVariant(appData.variant)) {
849         // we changed variant when loading the engine; this forces us to reset
850         Reset(TRUE, savCps != &first);
851         EditGameEvent(); // for consistency with other path, as Reset changes mode
852     }
853     InitChessProgram(savCps, FALSE);
854     SendToProgram("force\n", savCps);
855     DisplayMessage("", "");
856     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
857     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
858     ThawUI();
859     SetGNUMode();
860 }
861
862 void
863 ReplaceEngine(ChessProgramState *cps, int n)
864 {
865     EditGameEvent();
866     UnloadEngine(cps);
867     appData.noChessProgram = FALSE;
868     appData.clockMode = TRUE;
869     InitEngine(cps, n);
870     UpdateLogos(TRUE);
871     if(n) return; // only startup first engine immediately; second can wait
872     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873     LoadEngine();
874 }
875
876 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
877 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878
879 static char resetOptions[] = 
880         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
881         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
882         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     DisplayBook(current+1);
1669
1670     MoveHistorySet( movelist, first, last, current, pvInfoList );
1671
1672     EvalGraphSet( first, last, current, pvInfoList );
1673
1674     MakeEngineOutputTitle();
1675 }
1676
1677 /*
1678  * Establish will establish a contact to a remote host.port.
1679  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1680  *  used to talk to the host.
1681  * Returns 0 if okay, error code if not.
1682  */
1683 int
1684 establish()
1685 {
1686     char buf[MSG_SIZ];
1687
1688     if (*appData.icsCommPort != NULLCHAR) {
1689         /* Talk to the host through a serial comm port */
1690         return OpenCommPort(appData.icsCommPort, &icsPR);
1691
1692     } else if (*appData.gateway != NULLCHAR) {
1693         if (*appData.remoteShell == NULLCHAR) {
1694             /* Use the rcmd protocol to run telnet program on a gateway host */
1695             snprintf(buf, sizeof(buf), "%s %s %s",
1696                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1697             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1698
1699         } else {
1700             /* Use the rsh program to run telnet program on a gateway host */
1701             if (*appData.remoteUser == NULLCHAR) {
1702                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1703                         appData.gateway, appData.telnetProgram,
1704                         appData.icsHost, appData.icsPort);
1705             } else {
1706                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1707                         appData.remoteShell, appData.gateway,
1708                         appData.remoteUser, appData.telnetProgram,
1709                         appData.icsHost, appData.icsPort);
1710             }
1711             return StartChildProcess(buf, "", &icsPR);
1712
1713         }
1714     } else if (appData.useTelnet) {
1715         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1716
1717     } else {
1718         /* TCP socket interface differs somewhat between
1719            Unix and NT; handle details in the front end.
1720            */
1721         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1722     }
1723 }
1724
1725 void EscapeExpand(char *p, char *q)
1726 {       // [HGM] initstring: routine to shape up string arguments
1727         while(*p++ = *q++) if(p[-1] == '\\')
1728             switch(*q++) {
1729                 case 'n': p[-1] = '\n'; break;
1730                 case 'r': p[-1] = '\r'; break;
1731                 case 't': p[-1] = '\t'; break;
1732                 case '\\': p[-1] = '\\'; break;
1733                 case 0: *p = 0; return;
1734                 default: p[-1] = q[-1]; break;
1735             }
1736 }
1737
1738 void
1739 show_bytes(fp, buf, count)
1740      FILE *fp;
1741      char *buf;
1742      int count;
1743 {
1744     while (count--) {
1745         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1746             fprintf(fp, "\\%03o", *buf & 0xff);
1747         } else {
1748             putc(*buf, fp);
1749         }
1750         buf++;
1751     }
1752     fflush(fp);
1753 }
1754
1755 /* Returns an errno value */
1756 int
1757 OutputMaybeTelnet(pr, message, count, outError)
1758      ProcRef pr;
1759      char *message;
1760      int count;
1761      int *outError;
1762 {
1763     char buf[8192], *p, *q, *buflim;
1764     int left, newcount, outcount;
1765
1766     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1767         *appData.gateway != NULLCHAR) {
1768         if (appData.debugMode) {
1769             fprintf(debugFP, ">ICS: ");
1770             show_bytes(debugFP, message, count);
1771             fprintf(debugFP, "\n");
1772         }
1773         return OutputToProcess(pr, message, count, outError);
1774     }
1775
1776     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1777     p = message;
1778     q = buf;
1779     left = count;
1780     newcount = 0;
1781     while (left) {
1782         if (q >= buflim) {
1783             if (appData.debugMode) {
1784                 fprintf(debugFP, ">ICS: ");
1785                 show_bytes(debugFP, buf, newcount);
1786                 fprintf(debugFP, "\n");
1787             }
1788             outcount = OutputToProcess(pr, buf, newcount, outError);
1789             if (outcount < newcount) return -1; /* to be sure */
1790             q = buf;
1791             newcount = 0;
1792         }
1793         if (*p == '\n') {
1794             *q++ = '\r';
1795             newcount++;
1796         } else if (((unsigned char) *p) == TN_IAC) {
1797             *q++ = (char) TN_IAC;
1798             newcount ++;
1799         }
1800         *q++ = *p++;
1801         newcount++;
1802         left--;
1803     }
1804     if (appData.debugMode) {
1805         fprintf(debugFP, ">ICS: ");
1806         show_bytes(debugFP, buf, newcount);
1807         fprintf(debugFP, "\n");
1808     }
1809     outcount = OutputToProcess(pr, buf, newcount, outError);
1810     if (outcount < newcount) return -1; /* to be sure */
1811     return count;
1812 }
1813
1814 void
1815 read_from_player(isr, closure, message, count, error)
1816      InputSourceRef isr;
1817      VOIDSTAR closure;
1818      char *message;
1819      int count;
1820      int error;
1821 {
1822     int outError, outCount;
1823     static int gotEof = 0;
1824
1825     /* Pass data read from player on to ICS */
1826     if (count > 0) {
1827         gotEof = 0;
1828         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1829         if (outCount < count) {
1830             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831         }
1832     } else if (count < 0) {
1833         RemoveInputSource(isr);
1834         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1835     } else if (gotEof++ > 0) {
1836         RemoveInputSource(isr);
1837         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1838     }
1839 }
1840
1841 void
1842 KeepAlive()
1843 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1844     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1845     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1846     SendToICS("date\n");
1847     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1848 }
1849
1850 /* added routine for printf style output to ics */
1851 void ics_printf(char *format, ...)
1852 {
1853     char buffer[MSG_SIZ];
1854     va_list args;
1855
1856     va_start(args, format);
1857     vsnprintf(buffer, sizeof(buffer), format, args);
1858     buffer[sizeof(buffer)-1] = '\0';
1859     SendToICS(buffer);
1860     va_end(args);
1861 }
1862
1863 void
1864 SendToICS(s)
1865      char *s;
1866 {
1867     int count, outCount, outError;
1868
1869     if (icsPR == NoProc) return;
1870
1871     count = strlen(s);
1872     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1873     if (outCount < count) {
1874         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1875     }
1876 }
1877
1878 /* This is used for sending logon scripts to the ICS. Sending
1879    without a delay causes problems when using timestamp on ICC
1880    (at least on my machine). */
1881 void
1882 SendToICSDelayed(s,msdelay)
1883      char *s;
1884      long msdelay;
1885 {
1886     int count, outCount, outError;
1887
1888     if (icsPR == NoProc) return;
1889
1890     count = strlen(s);
1891     if (appData.debugMode) {
1892         fprintf(debugFP, ">ICS: ");
1893         show_bytes(debugFP, s, count);
1894         fprintf(debugFP, "\n");
1895     }
1896     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1897                                       msdelay);
1898     if (outCount < count) {
1899         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900     }
1901 }
1902
1903
1904 /* Remove all highlighting escape sequences in s
1905    Also deletes any suffix starting with '('
1906    */
1907 char *
1908 StripHighlightAndTitle(s)
1909      char *s;
1910 {
1911     static char retbuf[MSG_SIZ];
1912     char *p = retbuf;
1913
1914     while (*s != NULLCHAR) {
1915         while (*s == '\033') {
1916             while (*s != NULLCHAR && !isalpha(*s)) s++;
1917             if (*s != NULLCHAR) s++;
1918         }
1919         while (*s != NULLCHAR && *s != '\033') {
1920             if (*s == '(' || *s == '[') {
1921                 *p = NULLCHAR;
1922                 return retbuf;
1923             }
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 /* Remove all highlighting escape sequences in s */
1932 char *
1933 StripHighlight(s)
1934      char *s;
1935 {
1936     static char retbuf[MSG_SIZ];
1937     char *p = retbuf;
1938
1939     while (*s != NULLCHAR) {
1940         while (*s == '\033') {
1941             while (*s != NULLCHAR && !isalpha(*s)) s++;
1942             if (*s != NULLCHAR) s++;
1943         }
1944         while (*s != NULLCHAR && *s != '\033') {
1945             *p++ = *s++;
1946         }
1947     }
1948     *p = NULLCHAR;
1949     return retbuf;
1950 }
1951
1952 char *variantNames[] = VARIANT_NAMES;
1953 char *
1954 VariantName(v)
1955      VariantClass v;
1956 {
1957     return variantNames[v];
1958 }
1959
1960
1961 /* Identify a variant from the strings the chess servers use or the
1962    PGN Variant tag names we use. */
1963 VariantClass
1964 StringToVariant(e)
1965      char *e;
1966 {
1967     char *p;
1968     int wnum = -1;
1969     VariantClass v = VariantNormal;
1970     int i, found = FALSE;
1971     char buf[MSG_SIZ];
1972     int len;
1973
1974     if (!e) return v;
1975
1976     /* [HGM] skip over optional board-size prefixes */
1977     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1978         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1979         while( *e++ != '_');
1980     }
1981
1982     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1983         v = VariantNormal;
1984         found = TRUE;
1985     } else
1986     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1987       if (StrCaseStr(e, variantNames[i])) {
1988         v = (VariantClass) i;
1989         found = TRUE;
1990         break;
1991       }
1992     }
1993
1994     if (!found) {
1995       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1996           || StrCaseStr(e, "wild/fr")
1997           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1998         v = VariantFischeRandom;
1999       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2000                  (i = 1, p = StrCaseStr(e, "w"))) {
2001         p += i;
2002         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2003         if (isdigit(*p)) {
2004           wnum = atoi(p);
2005         } else {
2006           wnum = -1;
2007         }
2008         switch (wnum) {
2009         case 0: /* FICS only, actually */
2010         case 1:
2011           /* Castling legal even if K starts on d-file */
2012           v = VariantWildCastle;
2013           break;
2014         case 2:
2015         case 3:
2016         case 4:
2017           /* Castling illegal even if K & R happen to start in
2018              normal positions. */
2019           v = VariantNoCastle;
2020           break;
2021         case 5:
2022         case 7:
2023         case 8:
2024         case 10:
2025         case 11:
2026         case 12:
2027         case 13:
2028         case 14:
2029         case 15:
2030         case 18:
2031         case 19:
2032           /* Castling legal iff K & R start in normal positions */
2033           v = VariantNormal;
2034           break;
2035         case 6:
2036         case 20:
2037         case 21:
2038           /* Special wilds for position setup; unclear what to do here */
2039           v = VariantLoadable;
2040           break;
2041         case 9:
2042           /* Bizarre ICC game */
2043           v = VariantTwoKings;
2044           break;
2045         case 16:
2046           v = VariantKriegspiel;
2047           break;
2048         case 17:
2049           v = VariantLosers;
2050           break;
2051         case 22:
2052           v = VariantFischeRandom;
2053           break;
2054         case 23:
2055           v = VariantCrazyhouse;
2056           break;
2057         case 24:
2058           v = VariantBughouse;
2059           break;
2060         case 25:
2061           v = Variant3Check;
2062           break;
2063         case 26:
2064           /* Not quite the same as FICS suicide! */
2065           v = VariantGiveaway;
2066           break;
2067         case 27:
2068           v = VariantAtomic;
2069           break;
2070         case 28:
2071           v = VariantShatranj;
2072           break;
2073
2074         /* Temporary names for future ICC types.  The name *will* change in
2075            the next xboard/WinBoard release after ICC defines it. */
2076         case 29:
2077           v = Variant29;
2078           break;
2079         case 30:
2080           v = Variant30;
2081           break;
2082         case 31:
2083           v = Variant31;
2084           break;
2085         case 32:
2086           v = Variant32;
2087           break;
2088         case 33:
2089           v = Variant33;
2090           break;
2091         case 34:
2092           v = Variant34;
2093           break;
2094         case 35:
2095           v = Variant35;
2096           break;
2097         case 36:
2098           v = Variant36;
2099           break;
2100         case 37:
2101           v = VariantShogi;
2102           break;
2103         case 38:
2104           v = VariantXiangqi;
2105           break;
2106         case 39:
2107           v = VariantCourier;
2108           break;
2109         case 40:
2110           v = VariantGothic;
2111           break;
2112         case 41:
2113           v = VariantCapablanca;
2114           break;
2115         case 42:
2116           v = VariantKnightmate;
2117           break;
2118         case 43:
2119           v = VariantFairy;
2120           break;
2121         case 44:
2122           v = VariantCylinder;
2123           break;
2124         case 45:
2125           v = VariantFalcon;
2126           break;
2127         case 46:
2128           v = VariantCapaRandom;
2129           break;
2130         case 47:
2131           v = VariantBerolina;
2132           break;
2133         case 48:
2134           v = VariantJanus;
2135           break;
2136         case 49:
2137           v = VariantSuper;
2138           break;
2139         case 50:
2140           v = VariantGreat;
2141           break;
2142         case -1:
2143           /* Found "wild" or "w" in the string but no number;
2144              must assume it's normal chess. */
2145           v = VariantNormal;
2146           break;
2147         default:
2148           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2149           if( (len > MSG_SIZ) && appData.debugMode )
2150             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2151
2152           DisplayError(buf, 0);
2153           v = VariantUnknown;
2154           break;
2155         }
2156       }
2157     }
2158     if (appData.debugMode) {
2159       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2160               e, wnum, VariantName(v));
2161     }
2162     return v;
2163 }
2164
2165 static int leftover_start = 0, leftover_len = 0;
2166 char star_match[STAR_MATCH_N][MSG_SIZ];
2167
2168 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2169    advance *index beyond it, and set leftover_start to the new value of
2170    *index; else return FALSE.  If pattern contains the character '*', it
2171    matches any sequence of characters not containing '\r', '\n', or the
2172    character following the '*' (if any), and the matched sequence(s) are
2173    copied into star_match.
2174    */
2175 int
2176 looking_at(buf, index, pattern)
2177      char *buf;
2178      int *index;
2179      char *pattern;
2180 {
2181     char *bufp = &buf[*index], *patternp = pattern;
2182     int star_count = 0;
2183     char *matchp = star_match[0];
2184
2185     for (;;) {
2186         if (*patternp == NULLCHAR) {
2187             *index = leftover_start = bufp - buf;
2188             *matchp = NULLCHAR;
2189             return TRUE;
2190         }
2191         if (*bufp == NULLCHAR) return FALSE;
2192         if (*patternp == '*') {
2193             if (*bufp == *(patternp + 1)) {
2194                 *matchp = NULLCHAR;
2195                 matchp = star_match[++star_count];
2196                 patternp += 2;
2197                 bufp++;
2198                 continue;
2199             } else if (*bufp == '\n' || *bufp == '\r') {
2200                 patternp++;
2201                 if (*patternp == NULLCHAR)
2202                   continue;
2203                 else
2204                   return FALSE;
2205             } else {
2206                 *matchp++ = *bufp++;
2207                 continue;
2208             }
2209         }
2210         if (*patternp != *bufp) return FALSE;
2211         patternp++;
2212         bufp++;
2213     }
2214 }
2215
2216 void
2217 SendToPlayer(data, length)
2218      char *data;
2219      int length;
2220 {
2221     int error, outCount;
2222     outCount = OutputToProcess(NoProc, data, length, &error);
2223     if (outCount < length) {
2224         DisplayFatalError(_("Error writing to display"), error, 1);
2225     }
2226 }
2227
2228 void
2229 PackHolding(packed, holding)
2230      char packed[];
2231      char *holding;
2232 {
2233     char *p = holding;
2234     char *q = packed;
2235     int runlength = 0;
2236     int curr = 9999;
2237     do {
2238         if (*p == curr) {
2239             runlength++;
2240         } else {
2241             switch (runlength) {
2242               case 0:
2243                 break;
2244               case 1:
2245                 *q++ = curr;
2246                 break;
2247               case 2:
2248                 *q++ = curr;
2249                 *q++ = curr;
2250                 break;
2251               default:
2252                 sprintf(q, "%d", runlength);
2253                 while (*q) q++;
2254                 *q++ = curr;
2255                 break;
2256             }
2257             runlength = 1;
2258             curr = *p;
2259         }
2260     } while (*p++);
2261     *q = NULLCHAR;
2262 }
2263
2264 /* Telnet protocol requests from the front end */
2265 void
2266 TelnetRequest(ddww, option)
2267      unsigned char ddww, option;
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch(Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd(int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot(int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd(int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine(char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int SeekGraphClick(ClickType click, int x, int y, int moving)
2615 {
2616     static int lastDown = 0, displayed = 0, lastSecond;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics(isr, closure, data, count, error)
2667      InputSourceRef isr;
2668      VOIDSTAR closure;
2669      char *data;
2670      int count;
2671      int error;
2672 {
2673 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2674 #define STARTED_NONE 0
2675 #define STARTED_MOVES 1
2676 #define STARTED_BOARD 2
2677 #define STARTED_OBSERVE 3
2678 #define STARTED_HOLDINGS 4
2679 #define STARTED_CHATTER 5
2680 #define STARTED_COMMENT 6
2681 #define STARTED_MOVES_NOHIDE 7
2682
2683     static int started = STARTED_NONE;
2684     static char parse[20000];
2685     static int parse_pos = 0;
2686     static char buf[BUF_SIZE + 1];
2687     static int firstTime = TRUE, intfSet = FALSE;
2688     static ColorClass prevColor = ColorNormal;
2689     static int savingComment = FALSE;
2690     static int cmatch = 0; // continuation sequence match
2691     char *bp;
2692     char str[MSG_SIZ];
2693     int i, oldi;
2694     int buf_len;
2695     int next_out;
2696     int tkind;
2697     int backup;    /* [DM] For zippy color lines */
2698     char *p;
2699     char talker[MSG_SIZ]; // [HGM] chat
2700     int channel;
2701
2702     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2703
2704     if (appData.debugMode) {
2705       if (!error) {
2706         fprintf(debugFP, "<ICS: ");
2707         show_bytes(debugFP, data, count);
2708         fprintf(debugFP, "\n");
2709       }
2710     }
2711
2712     if (appData.debugMode) { int f = forwardMostMove;
2713         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2714                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2715                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2716     }
2717     if (count > 0) {
2718         /* If last read ended with a partial line that we couldn't parse,
2719            prepend it to the new read and try again. */
2720         if (leftover_len > 0) {
2721             for (i=0; i<leftover_len; i++)
2722               buf[i] = buf[leftover_start + i];
2723         }
2724
2725     /* copy new characters into the buffer */
2726     bp = buf + leftover_len;
2727     buf_len=leftover_len;
2728     for (i=0; i<count; i++)
2729     {
2730         // ignore these
2731         if (data[i] == '\r')
2732             continue;
2733
2734         // join lines split by ICS?
2735         if (!appData.noJoin)
2736         {
2737             /*
2738                 Joining just consists of finding matches against the
2739                 continuation sequence, and discarding that sequence
2740                 if found instead of copying it.  So, until a match
2741                 fails, there's nothing to do since it might be the
2742                 complete sequence, and thus, something we don't want
2743                 copied.
2744             */
2745             if (data[i] == cont_seq[cmatch])
2746             {
2747                 cmatch++;
2748                 if (cmatch == strlen(cont_seq))
2749                 {
2750                     cmatch = 0; // complete match.  just reset the counter
2751
2752                     /*
2753                         it's possible for the ICS to not include the space
2754                         at the end of the last word, making our [correct]
2755                         join operation fuse two separate words.  the server
2756                         does this when the space occurs at the width setting.
2757                     */
2758                     if (!buf_len || buf[buf_len-1] != ' ')
2759                     {
2760                         *bp++ = ' ';
2761                         buf_len++;
2762                     }
2763                 }
2764                 continue;
2765             }
2766             else if (cmatch)
2767             {
2768                 /*
2769                     match failed, so we have to copy what matched before
2770                     falling through and copying this character.  In reality,
2771                     this will only ever be just the newline character, but
2772                     it doesn't hurt to be precise.
2773                 */
2774                 strncpy(bp, cont_seq, cmatch);
2775                 bp += cmatch;
2776                 buf_len += cmatch;
2777                 cmatch = 0;
2778             }
2779         }
2780
2781         // copy this char
2782         *bp++ = data[i];
2783         buf_len++;
2784     }
2785
2786         buf[buf_len] = NULLCHAR;
2787 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2788         next_out = 0;
2789         leftover_start = 0;
2790
2791         i = 0;
2792         while (i < buf_len) {
2793             /* Deal with part of the TELNET option negotiation
2794                protocol.  We refuse to do anything beyond the
2795                defaults, except that we allow the WILL ECHO option,
2796                which ICS uses to turn off password echoing when we are
2797                directly connected to it.  We reject this option
2798                if localLineEditing mode is on (always on in xboard)
2799                and we are talking to port 23, which might be a real
2800                telnet server that will try to keep WILL ECHO on permanently.
2801              */
2802             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2803                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2804                 unsigned char option;
2805                 oldi = i;
2806                 switch ((unsigned char) buf[++i]) {
2807                   case TN_WILL:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WILL ");
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                         if (appData.localLineEditing &&
2818                             atoi(appData.icsPort) == TN_PORT) {
2819                             TelnetRequest(TN_DONT, TN_ECHO);
2820                         } else {
2821                             EchoOff();
2822                             TelnetRequest(TN_DO, TN_ECHO);
2823                             remoteEchoOption = TRUE;
2824                         }
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         /* Whatever this is, we don't want it. */
2830                         TelnetRequest(TN_DONT, option);
2831                         break;
2832                     }
2833                     break;
2834                   case TN_WONT:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<WONT ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       case TN_ECHO:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "ECHO ");
2841                         /* Reply only if this is a change, according
2842                            to the protocol rules. */
2843                         if (!remoteEchoOption) break;
2844                         EchoOn();
2845                         TelnetRequest(TN_DONT, TN_ECHO);
2846                         remoteEchoOption = FALSE;
2847                         break;
2848                       default:
2849                         if (appData.debugMode)
2850                           fprintf(debugFP, "%d ", (unsigned char) option);
2851                         /* Whatever this is, it must already be turned
2852                            off, because we never agree to turn on
2853                            anything non-default, so according to the
2854                            protocol rules, we don't reply. */
2855                         break;
2856                     }
2857                     break;
2858                   case TN_DO:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<DO ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       default:
2863                         /* Whatever this is, we refuse to do it. */
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         TelnetRequest(TN_WONT, option);
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DONT:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DONT ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         if (appData.debugMode)
2876                           fprintf(debugFP, "%d ", option);
2877                         /* Whatever this is, we are already not doing
2878                            it, because we never agree to do anything
2879                            non-default, so according to the protocol
2880                            rules, we don't reply. */
2881                         break;
2882                     }
2883                     break;
2884                   case TN_IAC:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<IAC ");
2887                     /* Doubled IAC; pass it through */
2888                     i--;
2889                     break;
2890                   default:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2893                     /* Drop all other telnet commands on the floor */
2894                     break;
2895                 }
2896                 if (oldi > next_out)
2897                   SendToPlayer(&buf[next_out], oldi - next_out);
2898                 if (++i > next_out)
2899                   next_out = i;
2900                 continue;
2901             }
2902
2903             /* OK, this at least will *usually* work */
2904             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2905                 loggedOn = TRUE;
2906             }
2907
2908             if (loggedOn && !intfSet) {
2909                 if (ics_type == ICS_ICC) {
2910                   snprintf(str, MSG_SIZ,
2911                           "/set-quietly interface %s\n/set-quietly style 12\n",
2912                           programVersion);
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2915                 } else if (ics_type == ICS_CHESSNET) {
2916                   snprintf(str, MSG_SIZ, "/style 12\n");
2917                 } else {
2918                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2919                   strcat(str, programVersion);
2920                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2921                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2923 #ifdef WIN32
2924                   strcat(str, "$iset nohighlight 1\n");
2925 #endif
2926                   strcat(str, "$iset lock 1\n$style 12\n");
2927                 }
2928                 SendToICS(str);
2929                 NotifyFrontendLogin();
2930                 intfSet = TRUE;
2931             }
2932
2933             if (started == STARTED_COMMENT) {
2934                 /* Accumulate characters in comment */
2935                 parse[parse_pos++] = buf[i];
2936                 if (buf[i] == '\n') {
2937                     parse[parse_pos] = NULLCHAR;
2938                     if(chattingPartner>=0) {
2939                         char mess[MSG_SIZ];
2940                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2941                         OutputChatMessage(chattingPartner, mess);
2942                         chattingPartner = -1;
2943                         next_out = i+1; // [HGM] suppress printing in ICS window
2944                     } else
2945                     if(!suppressKibitz) // [HGM] kibitz
2946                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2947                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2948                         int nrDigit = 0, nrAlph = 0, j;
2949                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2950                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2951                         parse[parse_pos] = NULLCHAR;
2952                         // try to be smart: if it does not look like search info, it should go to
2953                         // ICS interaction window after all, not to engine-output window.
2954                         for(j=0; j<parse_pos; j++) { // count letters and digits
2955                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2956                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2957                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2958                         }
2959                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2960                             int depth=0; float score;
2961                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2962                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2963                                 pvInfoList[forwardMostMove-1].depth = depth;
2964                                 pvInfoList[forwardMostMove-1].score = 100*score;
2965                             }
2966                             OutputKibitz(suppressKibitz, parse);
2967                         } else {
2968                             char tmp[MSG_SIZ];
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
3008                 ics_prefix = "$";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3103                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3104                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3105                         suppressKibitz = TRUE;
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3109                                 && (gameMode == IcsPlayingWhite)) ||
3110                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3111                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3112                             started = STARTED_CHATTER; // own kibitz we simply discard
3113                         else {
3114                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3115                             parse_pos = 0; parse[0] = NULLCHAR;
3116                             savingComment = TRUE;
3117                             suppressKibitz = gameMode != IcsObserving ? 2 :
3118                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3119                         }
3120                         continue;
3121                 } else
3122                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3123                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3124                          && atoi(star_match[0])) {
3125                     // suppress the acknowledgements of our own autoKibitz
3126                     char *p;
3127                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3129                     SendToPlayer(star_match[0], strlen(star_match[0]));
3130                     if(looking_at(buf, &i, "*% ")) // eat prompt
3131                         suppressKibitz = FALSE;
3132                     next_out = i;
3133                     continue;
3134                 }
3135             } // [HGM] kibitz: end of patch
3136
3137             // [HGM] chat: intercept tells by users for which we have an open chat window
3138             channel = -1;
3139             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3140                                            looking_at(buf, &i, "* whispers:") ||
3141                                            looking_at(buf, &i, "* kibitzes:") ||
3142                                            looking_at(buf, &i, "* shouts:") ||
3143                                            looking_at(buf, &i, "* c-shouts:") ||
3144                                            looking_at(buf, &i, "--> * ") ||
3145                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149                 int p;
3150                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3151                 chattingPartner = -1;
3152
3153                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3156                     talker[0] = '['; strcat(talker, "] ");
3157                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3158                     chattingPartner = p; break;
3159                     }
3160                 } else
3161                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3162                 for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("kibitzes", chatPartner[p])) {
3164                         talker[0] = '['; strcat(talker, "] ");
3165                         chattingPartner = p; break;
3166                     }
3167                 } else
3168                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3169                 for(p=0; p<MAX_CHAT; p++) {
3170                     if(!strcmp("whispers", chatPartner[p])) {
3171                         talker[0] = '['; strcat(talker, "] ");
3172                         chattingPartner = p; break;
3173                     }
3174                 } else
3175                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3176                   if(buf[i-8] == '-' && buf[i-3] == 't')
3177                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3178                     if(!strcmp("c-shouts", chatPartner[p])) {
3179                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                   if(chattingPartner < 0)
3184                   for(p=0; p<MAX_CHAT; p++) {
3185                     if(!strcmp("shouts", chatPartner[p])) {
3186                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3187                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3188                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3189                         chattingPartner = p; break;
3190                     }
3191                   }
3192                 }
3193                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3194                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3195                     talker[0] = 0; Colorize(ColorTell, FALSE);
3196                     chattingPartner = p; break;
3197                 }
3198                 if(chattingPartner<0) i = oldi; else {
3199                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3200                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3201                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                     started = STARTED_COMMENT;
3203                     parse_pos = 0; parse[0] = NULLCHAR;
3204                     savingComment = 3 + chattingPartner; // counts as TRUE
3205                     suppressKibitz = TRUE;
3206                     continue;
3207                 }
3208             } // [HGM] chat: end of patch
3209
3210           backup = i;
3211             if (appData.zippyTalk || appData.zippyPlay) {
3212                 /* [DM] Backup address for color zippy lines */
3213 #if ZIPPY
3214                if (loggedOn == TRUE)
3215                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3216                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 #endif
3218             } // [DM] 'else { ' deleted
3219                 if (
3220                     /* Regular tells and says */
3221                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3222                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3223                     looking_at(buf, &i, "* says: ") ||
3224                     /* Don't color "message" or "messages" output */
3225                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3226                     looking_at(buf, &i, "*. * at *:*: ") ||
3227                     looking_at(buf, &i, "--* (*:*): ") ||
3228                     /* Message notifications (same color as tells) */
3229                     looking_at(buf, &i, "* has left a message ") ||
3230                     looking_at(buf, &i, "* just sent you a message:\n") ||
3231                     /* Whispers and kibitzes */
3232                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3233                     looking_at(buf, &i, "* kibitzes: ") ||
3234                     /* Channel tells */
3235                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236
3237                   if (tkind == 1 && strchr(star_match[0], ':')) {
3238                       /* Avoid "tells you:" spoofs in channels */
3239                      tkind = 3;
3240                   }
3241                   if (star_match[0][0] == NULLCHAR ||
3242                       strchr(star_match[0], ' ') ||
3243                       (tkind == 3 && strchr(star_match[1], ' '))) {
3244                     /* Reject bogus matches */
3245                     i = oldi;
3246                   } else {
3247                     if (appData.colorize) {
3248                       if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                         next_out = oldi;
3251                       }
3252                       switch (tkind) {
3253                       case 1:
3254                         Colorize(ColorTell, FALSE);
3255                         curColor = ColorTell;
3256                         break;
3257                       case 2:
3258                         Colorize(ColorKibitz, FALSE);
3259                         curColor = ColorKibitz;
3260                         break;
3261                       case 3:
3262                         p = strrchr(star_match[1], '(');
3263                         if (p == NULL) {
3264                           p = star_match[1];
3265                         } else {
3266                           p++;
3267                         }
3268                         if (atoi(p) == 1) {
3269                           Colorize(ColorChannel1, FALSE);
3270                           curColor = ColorChannel1;
3271                         } else {
3272                           Colorize(ColorChannel, FALSE);
3273                           curColor = ColorChannel;
3274                         }
3275                         break;
3276                       case 5:
3277                         curColor = ColorNormal;
3278                         break;
3279                       }
3280                     }
3281                     if (started == STARTED_NONE && appData.autoComment &&
3282                         (gameMode == IcsObserving ||
3283                          gameMode == IcsPlayingWhite ||
3284                          gameMode == IcsPlayingBlack)) {
3285                       parse_pos = i - oldi;
3286                       memcpy(parse, &buf[oldi], parse_pos);
3287                       parse[parse_pos] = NULLCHAR;
3288                       started = STARTED_COMMENT;
3289                       savingComment = TRUE;
3290                     } else {
3291                       started = STARTED_CHATTER;
3292                       savingComment = FALSE;
3293                     }
3294                     loggedOn = TRUE;
3295                     continue;
3296                   }
3297                 }
3298
3299                 if (looking_at(buf, &i, "* s-shouts: ") ||
3300                     looking_at(buf, &i, "* c-shouts: ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorSShout, FALSE);
3307                         curColor = ColorSShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at(buf, &i, "--->")) {
3315                     loggedOn = TRUE;
3316                     continue;
3317                 }
3318
3319                 if (looking_at(buf, &i, "* shouts: ") ||
3320                     looking_at(buf, &i, "--> ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorShout, FALSE);
3327                         curColor = ColorShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at( buf, &i, "Challenge:")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorChallenge, FALSE);
3341                         curColor = ColorChallenge;
3342                     }
3343                     loggedOn = TRUE;
3344                     continue;
3345                 }
3346
3347                 if (looking_at(buf, &i, "* offers you") ||
3348                     looking_at(buf, &i, "* offers to be") ||
3349                     looking_at(buf, &i, "* would like to") ||
3350                     looking_at(buf, &i, "* requests to") ||
3351                     looking_at(buf, &i, "Your opponent offers") ||
3352                     looking_at(buf, &i, "Your opponent requests")) {
3353
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorRequest, FALSE);
3360                         curColor = ColorRequest;
3361                     }
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* (*) seeking")) {
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorSeek, FALSE);
3372                         curColor = ColorSeek;
3373                     }
3374                     continue;
3375             }
3376
3377           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378
3379             if (looking_at(buf, &i, "\\   ")) {
3380                 if (prevColor != ColorNormal) {
3381                     if (oldi > next_out) {
3382                         SendToPlayer(&buf[next_out], oldi - next_out);
3383                         next_out = oldi;
3384                     }
3385                     Colorize(prevColor, TRUE);
3386                     curColor = prevColor;
3387                 }
3388                 if (savingComment) {
3389                     parse_pos = i - oldi;
3390                     memcpy(parse, &buf[oldi], parse_pos);
3391                     parse[parse_pos] = NULLCHAR;
3392                     started = STARTED_COMMENT;
3393                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3394                         chattingPartner = savingComment - 3; // kludge to remember the box
3395                 } else {
3396                     started = STARTED_CHATTER;
3397                 }
3398                 continue;
3399             }
3400
3401             if (looking_at(buf, &i, "Black Strength :") ||
3402                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3403                 looking_at(buf, &i, "<10>") ||
3404                 looking_at(buf, &i, "#@#")) {
3405                 /* Wrong board style */
3406                 loggedOn = TRUE;
3407                 SendToICS(ics_prefix);
3408                 SendToICS("set style 12\n");
3409                 SendToICS(ics_prefix);
3410                 SendToICS("refresh\n");
3411                 continue;
3412             }
3413
3414             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415                 ICSInitScript();
3416                 have_sent_ICS_logon = 1;
3417                 continue;
3418             }
3419
3420             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3421                 (looking_at(buf, &i, "\n<12> ") ||
3422                  looking_at(buf, &i, "<12> "))) {
3423                 loggedOn = TRUE;
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_BOARD;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3434                 looking_at(buf, &i, "<b1> ")) {
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_HOLDINGS;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3445                 loggedOn = TRUE;
3446                 /* Header for a move list -- first line */
3447
3448                 switch (ics_getting_history) {
3449                   case H_FALSE:
3450                     switch (gameMode) {
3451                       case IcsIdle:
3452                       case BeginningOfGame:
3453                         /* User typed "moves" or "oldmoves" while we
3454                            were idle.  Pretend we asked for these
3455                            moves and soak them up so user can step
3456                            through them and/or save them.
3457                            */
3458                         Reset(FALSE, TRUE);
3459                         gameMode = IcsObserving;
3460                         ModeHighlight();
3461                         ics_gamenum = -1;
3462                         ics_getting_history = H_GOT_UNREQ_HEADER;
3463                         break;
3464                       case EditGame: /*?*/
3465                       case EditPosition: /*?*/
3466                         /* Should above feature work in these modes too? */
3467                         /* For now it doesn't */
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                       default:
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                     }
3474                     break;
3475                   case H_REQUESTED:
3476                     /* Is this the right one? */
3477                     if (gameInfo.white && gameInfo.black &&
3478                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3479                         strcmp(gameInfo.black, star_match[2]) == 0) {
3480                         /* All is well */
3481                         ics_getting_history = H_GOT_REQ_HEADER;
3482                     }
3483                     break;
3484                   case H_GOT_REQ_HEADER:
3485                   case H_GOT_UNREQ_HEADER:
3486                   case H_GOT_UNWANTED_HEADER:
3487                   case H_GETTING_MOVES:
3488                     /* Should not happen */
3489                     DisplayError(_("Error gathering move list: two headers"), 0);
3490                     ics_getting_history = H_FALSE;
3491                     break;
3492                 }
3493
3494                 /* Save player ratings into gameInfo if needed */
3495                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3496                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3497                     (gameInfo.whiteRating == -1 ||
3498                      gameInfo.blackRating == -1)) {
3499
3500                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3501                     gameInfo.blackRating = string_to_rating(star_match[3]);
3502                     if (appData.debugMode)
3503                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3504                               gameInfo.whiteRating, gameInfo.blackRating);
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i,
3510               "* * match, initial time: * minute*, increment: * second")) {
3511                 /* Header for a move list -- second line */
3512                 /* Initial board will follow if this is a wild game */
3513                 if (gameInfo.event != NULL) free(gameInfo.event);
3514                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3515                 gameInfo.event = StrSave(str);
3516                 /* [HGM] we switched variant. Translate boards if needed. */
3517                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i, "Move  ")) {
3522                 /* Beginning of a move list */
3523                 switch (ics_getting_history) {
3524                   case H_FALSE:
3525                     /* Normally should not happen */
3526                     /* Maybe user hit reset while we were parsing */
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Happens if we are ignoring a move list that is not
3530                      * the one we just requested.  Common if the user
3531                      * tries to observe two games without turning off
3532                      * getMoveList */
3533                     break;
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: nested"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                   case H_GOT_REQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES;
3542                     parse_pos = 0;
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                     }
3546                     break;
3547                   case H_GOT_UNREQ_HEADER:
3548                     ics_getting_history = H_GETTING_MOVES;
3549                     started = STARTED_MOVES_NOHIDE;
3550                     parse_pos = 0;
3551                     break;
3552                   case H_GOT_UNWANTED_HEADER:
3553                     ics_getting_history = H_FALSE;
3554                     break;
3555                 }
3556                 continue;
3557             }
3558
3559             if (looking_at(buf, &i, "% ") ||
3560                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3561                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3562                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3563                     soughtPending = FALSE;
3564                     seekGraphUp = TRUE;
3565                     DrawSeekGraph();
3566                 }
3567                 if(suppressKibitz) next_out = i;
3568                 savingComment = FALSE;
3569                 suppressKibitz = 0;
3570                 switch (started) {
3571                   case STARTED_MOVES:
3572                   case STARTED_MOVES_NOHIDE:
3573                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3574                     parse[parse_pos + i - oldi] = NULLCHAR;
3575                     ParseGameHistory(parse);
3576 #if ZIPPY
3577                     if (appData.zippyPlay && first.initDone) {
3578                         FeedMovesToProgram(&first, forwardMostMove);
3579                         if (gameMode == IcsPlayingWhite) {
3580                             if (WhiteOnMove(forwardMostMove)) {
3581                                 if (first.sendTime) {
3582                                   if (first.useColors) {
3583                                     SendToProgram("black\n", &first);
3584                                   }
3585                                   SendTimeRemaining(&first, TRUE);
3586                                 }
3587                                 if (first.useColors) {
3588                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3589                                 }
3590                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3591                                 first.maybeThinking = TRUE;
3592                             } else {
3593                                 if (first.usePlayother) {
3594                                   if (first.sendTime) {
3595                                     SendTimeRemaining(&first, TRUE);
3596                                   }
3597                                   SendToProgram("playother\n", &first);
3598                                   firstMove = FALSE;
3599                                 } else {
3600                                   firstMove = TRUE;
3601                                 }
3602                             }
3603                         } else if (gameMode == IcsPlayingBlack) {
3604                             if (!WhiteOnMove(forwardMostMove)) {
3605                                 if (first.sendTime) {
3606                                   if (first.useColors) {
3607                                     SendToProgram("white\n", &first);
3608                                   }
3609                                   SendTimeRemaining(&first, FALSE);
3610                                 }
3611                                 if (first.useColors) {
3612                                   SendToProgram("black\n", &first);
3613                                 }
3614                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3615                                 first.maybeThinking = TRUE;
3616                             } else {
3617                                 if (first.usePlayother) {
3618                                   if (first.sendTime) {
3619                                     SendTimeRemaining(&first, FALSE);
3620                                   }
3621                                   SendToProgram("playother\n", &first);
3622                                   firstMove = FALSE;
3623                                 } else {
3624                                   firstMove = TRUE;
3625                                 }
3626                             }
3627                         }
3628                     }
3629 #endif
3630                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3631                         /* Moves came from oldmoves or moves command
3632                            while we weren't doing anything else.
3633                            */
3634                         currentMove = forwardMostMove;
3635                         ClearHighlights();/*!!could figure this out*/
3636                         flipView = appData.flipView;
3637                         DrawPosition(TRUE, boards[currentMove]);
3638                         DisplayBothClocks();
3639                         snprintf(str, MSG_SIZ, "%s vs. %s",
3640                                 gameInfo.white, gameInfo.black);
3641                         DisplayTitle(str);
3642                         gameMode = IcsIdle;
3643                     } else {
3644                         /* Moves were history of an active game */
3645                         if (gameInfo.resultDetails != NULL) {
3646                             free(gameInfo.resultDetails);
3647                             gameInfo.resultDetails = NULL;
3648                         }
3649                     }
3650                     HistorySet(parseList, backwardMostMove,
3651                                forwardMostMove, currentMove-1);
3652                     DisplayMove(currentMove - 1);
3653                     if (started == STARTED_MOVES) next_out = i;
3654                     started = STARTED_NONE;
3655                     ics_getting_history = H_FALSE;
3656                     break;
3657
3658                   case STARTED_OBSERVE:
3659                     started = STARTED_NONE;
3660                     SendToICS(ics_prefix);
3661                     SendToICS("refresh\n");
3662                     break;
3663
3664                   default:
3665                     break;
3666                 }
3667                 if(bookHit) { // [HGM] book: simulate book reply
3668                     static char bookMove[MSG_SIZ]; // a bit generous?
3669
3670                     programStats.nodes = programStats.depth = programStats.time =
3671                     programStats.score = programStats.got_only_move = 0;
3672                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3673
3674                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3675                     strcat(bookMove, bookHit);
3676                     HandleMachineMove(bookMove, &first);
3677                 }
3678                 continue;
3679             }
3680
3681             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3682                  started == STARTED_HOLDINGS ||
3683                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3684                 /* Accumulate characters in move list or board */
3685                 parse[parse_pos++] = buf[i];
3686             }
3687
3688             /* Start of game messages.  Mostly we detect start of game
3689                when the first board image arrives.  On some versions
3690                of the ICS, though, we need to do a "refresh" after starting
3691                to observe in order to get the current board right away. */
3692             if (looking_at(buf, &i, "Adding game * to observation list")) {
3693                 started = STARTED_OBSERVE;
3694                 continue;
3695             }
3696
3697             /* Handle auto-observe */
3698             if (appData.autoObserve &&
3699                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3700                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3701                 char *player;
3702                 /* Choose the player that was highlighted, if any. */
3703                 if (star_match[0][0] == '\033' ||
3704                     star_match[1][0] != '\033') {
3705                     player = star_match[0];
3706                 } else {
3707                     player = star_match[2];
3708                 }
3709                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3710                         ics_prefix, StripHighlightAndTitle(player));
3711                 SendToICS(str);
3712
3713                 /* Save ratings from notify string */
3714                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3715                 player1Rating = string_to_rating(star_match[1]);
3716                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3717                 player2Rating = string_to_rating(star_match[3]);
3718
3719                 if (appData.debugMode)
3720                   fprintf(debugFP,
3721                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3722                           player1Name, player1Rating,
3723                           player2Name, player2Rating);
3724
3725                 continue;
3726             }
3727
3728             /* Deal with automatic examine mode after a game,
3729                and with IcsObserving -> IcsExamining transition */
3730             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3731                 looking_at(buf, &i, "has made you an examiner of game *")) {
3732
3733                 int gamenum = atoi(star_match[0]);
3734                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3735                     gamenum == ics_gamenum) {
3736                     /* We were already playing or observing this game;
3737                        no need to refetch history */
3738                     gameMode = IcsExamining;
3739                     if (pausing) {
3740                         pauseExamForwardMostMove = forwardMostMove;
3741                     } else if (currentMove < forwardMostMove) {
3742                         ForwardInner(forwardMostMove);
3743                     }
3744                 } else {
3745                     /* I don't think this case really can happen */
3746                     SendToICS(ics_prefix);
3747                     SendToICS("refresh\n");
3748                 }
3749                 continue;
3750             }
3751
3752             /* Error messages */
3753 //          if (ics_user_moved) {
3754             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3755                 if (looking_at(buf, &i, "Illegal move") ||
3756                     looking_at(buf, &i, "Not a legal move") ||
3757                     looking_at(buf, &i, "Your king is in check") ||
3758                     looking_at(buf, &i, "It isn't your turn") ||
3759                     looking_at(buf, &i, "It is not your move")) {
3760                     /* Illegal move */
3761                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3762                         currentMove = forwardMostMove-1;
3763                         DisplayMove(currentMove - 1); /* before DMError */
3764                         DrawPosition(FALSE, boards[currentMove]);
3765                         SwitchClocks(forwardMostMove-1); // [HGM] race
3766                         DisplayBothClocks();
3767                     }
3768                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3769                     ics_user_moved = 0;
3770                     continue;
3771                 }
3772             }
3773
3774             if (looking_at(buf, &i, "still have time") ||
3775                 looking_at(buf, &i, "not out of time") ||
3776                 looking_at(buf, &i, "either player is out of time") ||
3777                 looking_at(buf, &i, "has timeseal; checking")) {
3778                 /* We must have called his flag a little too soon */
3779                 whiteFlag = blackFlag = FALSE;
3780                 continue;
3781             }
3782
3783             if (looking_at(buf, &i, "added * seconds to") ||
3784                 looking_at(buf, &i, "seconds were added to")) {
3785                 /* Update the clocks */
3786                 SendToICS(ics_prefix);
3787                 SendToICS("refresh\n");
3788                 continue;
3789             }
3790
3791             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3792                 ics_clock_paused = TRUE;
3793                 StopClocks();
3794                 continue;
3795             }
3796
3797             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3798                 ics_clock_paused = FALSE;
3799                 StartClocks();
3800                 continue;
3801             }
3802
3803             /* Grab player ratings from the Creating: message.
3804                Note we have to check for the special case when
3805                the ICS inserts things like [white] or [black]. */
3806             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3807                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3808                 /* star_matches:
3809                    0    player 1 name (not necessarily white)
3810                    1    player 1 rating
3811                    2    empty, white, or black (IGNORED)
3812                    3    player 2 name (not necessarily black)
3813                    4    player 2 rating
3814
3815                    The names/ratings are sorted out when the game
3816                    actually starts (below).
3817                 */
3818                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3819                 player1Rating = string_to_rating(star_match[1]);
3820                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3821                 player2Rating = string_to_rating(star_match[4]);
3822
3823                 if (appData.debugMode)
3824                   fprintf(debugFP,
3825                           "Ratings from 'Creating:' %s %d, %s %d\n",
3826                           player1Name, player1Rating,
3827                           player2Name, player2Rating);
3828
3829                 continue;
3830             }
3831
3832             /* Improved generic start/end-of-game messages */
3833             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3834                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3835                 /* If tkind == 0: */
3836                 /* star_match[0] is the game number */
3837                 /*           [1] is the white player's name */
3838                 /*           [2] is the black player's name */
3839                 /* For end-of-game: */
3840                 /*           [3] is the reason for the game end */
3841                 /*           [4] is a PGN end game-token, preceded by " " */
3842                 /* For start-of-game: */
3843                 /*           [3] begins with "Creating" or "Continuing" */
3844                 /*           [4] is " *" or empty (don't care). */
3845                 int gamenum = atoi(star_match[0]);
3846                 char *whitename, *blackname, *why, *endtoken;
3847                 ChessMove endtype = EndOfFile;
3848
3849                 if (tkind == 0) {
3850                   whitename = star_match[1];
3851                   blackname = star_match[2];
3852                   why = star_match[3];
3853                   endtoken = star_match[4];
3854                 } else {
3855                   whitename = star_match[1];
3856                   blackname = star_match[3];
3857                   why = star_match[5];
3858                   endtoken = star_match[6];
3859                 }
3860
3861                 /* Game start messages */
3862                 if (strncmp(why, "Creating ", 9) == 0 ||
3863                     strncmp(why, "Continuing ", 11) == 0) {
3864                     gs_gamenum = gamenum;
3865                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3866                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3867                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3868 #if ZIPPY
3869                     if (appData.zippyPlay) {
3870                         ZippyGameStart(whitename, blackname);
3871                     }
3872 #endif /*ZIPPY*/
3873                     partnerBoardValid = FALSE; // [HGM] bughouse
3874                     continue;
3875                 }
3876
3877                 /* Game end messages */
3878                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3879                     ics_gamenum != gamenum) {
3880                     continue;
3881                 }
3882                 while (endtoken[0] == ' ') endtoken++;
3883                 switch (endtoken[0]) {
3884                   case '*':
3885                   default:
3886                     endtype = GameUnfinished;
3887                     break;
3888                   case '0':
3889                     endtype = BlackWins;
3890                     break;
3891                   case '1':
3892                     if (endtoken[1] == '/')
3893                       endtype = GameIsDrawn;
3894                     else
3895                       endtype = WhiteWins;
3896                     break;
3897                 }
3898                 GameEnds(endtype, why, GE_ICS);
3899 #if ZIPPY
3900                 if (appData.zippyPlay && first.initDone) {
3901                     ZippyGameEnd(endtype, why);
3902                     if (first.pr == NoProc) {
3903                       /* Start the next process early so that we'll
3904                          be ready for the next challenge */
3905                       StartChessProgram(&first);
3906                     }
3907                     /* Send "new" early, in case this command takes
3908                        a long time to finish, so that we'll be ready
3909                        for the next challenge. */
3910                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3911                     Reset(TRUE, TRUE);
3912                 }
3913 #endif /*ZIPPY*/
3914                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3915                 continue;
3916             }
3917
3918             if (looking_at(buf, &i, "Removing game * from observation") ||
3919                 looking_at(buf, &i, "no longer observing game *") ||
3920                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3921                 if (gameMode == IcsObserving &&
3922                     atoi(star_match[0]) == ics_gamenum)
3923                   {
3924                       /* icsEngineAnalyze */
3925                       if (appData.icsEngineAnalyze) {
3926                             ExitAnalyzeMode();
3927                             ModeHighlight();
3928                       }
3929                       StopClocks();
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "no longer examining game *")) {
3938                 if (gameMode == IcsExamining &&
3939                     atoi(star_match[0]) == ics_gamenum)
3940                   {
3941                       gameMode = IcsIdle;
3942                       ics_gamenum = -1;
3943                       ics_user_moved = FALSE;
3944                   }
3945                 continue;
3946             }
3947
3948             /* Advance leftover_start past any newlines we find,
3949                so only partial lines can get reparsed */
3950             if (looking_at(buf, &i, "\n")) {
3951                 prevColor = curColor;
3952                 if (curColor != ColorNormal) {
3953                     if (oldi > next_out) {
3954                         SendToPlayer(&buf[next_out], oldi - next_out);
3955                         next_out = oldi;
3956                     }
3957                     Colorize(ColorNormal, FALSE);
3958                     curColor = ColorNormal;
3959                 }
3960                 if (started == STARTED_BOARD) {
3961                     started = STARTED_NONE;
3962                     parse[parse_pos] = NULLCHAR;
3963                     ParseBoard12(parse);
3964                     ics_user_moved = 0;
3965
3966                     /* Send premove here */
3967                     if (appData.premove) {
3968                       char str[MSG_SIZ];
3969                       if (currentMove == 0 &&
3970                           gameMode == IcsPlayingWhite &&
3971                           appData.premoveWhite) {
3972                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3973                         if (appData.debugMode)
3974                           fprintf(debugFP, "Sending premove:\n");
3975                         SendToICS(str);
3976                       } else if (currentMove == 1 &&
3977                                  gameMode == IcsPlayingBlack &&
3978                                  appData.premoveBlack) {
3979                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3980                         if (appData.debugMode)
3981                           fprintf(debugFP, "Sending premove:\n");
3982                         SendToICS(str);
3983                       } else if (gotPremove) {
3984                         gotPremove = 0;
3985                         ClearPremoveHighlights();
3986                         if (appData.debugMode)
3987                           fprintf(debugFP, "Sending premove:\n");
3988                           UserMoveEvent(premoveFromX, premoveFromY,
3989                                         premoveToX, premoveToY,
3990                                         premovePromoChar);
3991                       }
3992                     }
3993
3994                     /* Usually suppress following prompt */
3995                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3996                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3997                         if (looking_at(buf, &i, "*% ")) {
3998                             savingComment = FALSE;
3999                             suppressKibitz = 0;
4000                         }
4001                     }
4002                     next_out = i;
4003                 } else if (started == STARTED_HOLDINGS) {
4004                     int gamenum;
4005                     char new_piece[MSG_SIZ];
4006                     started = STARTED_NONE;
4007                     parse[parse_pos] = NULLCHAR;
4008                     if (appData.debugMode)
4009                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4010                                                         parse, currentMove);
4011                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4012                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4013                         if (gameInfo.variant == VariantNormal) {
4014                           /* [HGM] We seem to switch variant during a game!
4015                            * Presumably no holdings were displayed, so we have
4016                            * to move the position two files to the right to
4017                            * create room for them!
4018                            */
4019                           VariantClass newVariant;
4020                           switch(gameInfo.boardWidth) { // base guess on board width
4021                                 case 9:  newVariant = VariantShogi; break;
4022                                 case 10: newVariant = VariantGreat; break;
4023                                 default: newVariant = VariantCrazyhouse; break;
4024                           }
4025                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4026                           /* Get a move list just to see the header, which
4027                              will tell us whether this is really bug or zh */
4028                           if (ics_getting_history == H_FALSE) {
4029                             ics_getting_history = H_REQUESTED;
4030                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4031                             SendToICS(str);
4032                           }
4033                         }
4034                         new_piece[0] = NULLCHAR;
4035                         sscanf(parse, "game %d white [%s black [%s <- %s",
4036                                &gamenum, white_holding, black_holding,
4037                                new_piece);
4038                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4039                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4040                         /* [HGM] copy holdings to board holdings area */
4041                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4042                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4043                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4044 #if ZIPPY
4045                         if (appData.zippyPlay && first.initDone) {
4046                             ZippyHoldings(white_holding, black_holding,
4047                                           new_piece);
4048                         }
4049 #endif /*ZIPPY*/
4050                         if (tinyLayout || smallLayout) {
4051                             char wh[16], bh[16];
4052                             PackHolding(wh, white_holding);
4053                             PackHolding(bh, black_holding);
4054                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4055                                     gameInfo.white, gameInfo.black);
4056                         } else {
4057                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4058                                     gameInfo.white, white_holding,
4059                                     gameInfo.black, black_holding);
4060                         }
4061                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4062                         DrawPosition(FALSE, boards[currentMove]);
4063                         DisplayTitle(str);
4064                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4065                         sscanf(parse, "game %d white [%s black [%s <- %s",
4066                                &gamenum, white_holding, black_holding,
4067                                new_piece);
4068                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4069                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4070                         /* [HGM] copy holdings to partner-board holdings area */
4071                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4072                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4073                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4074                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4075                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4076                       }
4077                     }
4078                     /* Suppress following prompt */
4079                     if (looking_at(buf, &i, "*% ")) {
4080                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4081                         savingComment = FALSE;
4082                         suppressKibitz = 0;
4083                     }
4084                     next_out = i;
4085                 }
4086                 continue;
4087             }
4088
4089             i++;                /* skip unparsed character and loop back */
4090         }
4091
4092         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4093 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4094 //          SendToPlayer(&buf[next_out], i - next_out);
4095             started != STARTED_HOLDINGS && leftover_start > next_out) {
4096             SendToPlayer(&buf[next_out], leftover_start - next_out);
4097             next_out = i;
4098         }
4099
4100         leftover_len = buf_len - leftover_start;
4101         /* if buffer ends with something we couldn't parse,
4102            reparse it after appending the next read */
4103
4104     } else if (count == 0) {
4105         RemoveInputSource(isr);
4106         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4107     } else {
4108         DisplayFatalError(_("Error reading from ICS"), error, 1);
4109     }
4110 }
4111
4112
4113 /* Board style 12 looks like this:
4114
4115    <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
4116
4117  * The "<12> " is stripped before it gets to this routine.  The two
4118  * trailing 0's (flip state and clock ticking) are later addition, and
4119  * some chess servers may not have them, or may have only the first.
4120  * Additional trailing fields may be added in the future.
4121  */
4122
4123 #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"
4124
4125 #define RELATION_OBSERVING_PLAYED    0
4126 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4127 #define RELATION_PLAYING_MYMOVE      1
4128 #define RELATION_PLAYING_NOTMYMOVE  -1
4129 #define RELATION_EXAMINING           2
4130 #define RELATION_ISOLATED_BOARD     -3
4131 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4132
4133 void
4134 ParseBoard12(string)
4135      char *string;
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     CopyBoard(boards[moveNum], board);
4443     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4444     if (moveNum == 0) {
4445         startedFromSetupPosition =
4446           !CompareBoards(board, initialPosition);
4447         if(startedFromSetupPosition)
4448             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4449     }
4450
4451     /* [HGM] Set castling rights. Take the outermost Rooks,
4452        to make it also work for FRC opening positions. Note that board12
4453        is really defective for later FRC positions, as it has no way to
4454        indicate which Rook can castle if they are on the same side of King.
4455        For the initial position we grant rights to the outermost Rooks,
4456        and remember thos rights, and we then copy them on positions
4457        later in an FRC game. This means WB might not recognize castlings with
4458        Rooks that have moved back to their original position as illegal,
4459        but in ICS mode that is not its job anyway.
4460     */
4461     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4462     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4463
4464         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4465             if(board[0][i] == WhiteRook) j = i;
4466         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4467         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4471             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4472         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476
4477         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4478         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4479             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[BOARD_HEIGHT-1][k] == bKing)
4482                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4483         if(gameInfo.variant == VariantTwoKings) {
4484             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4485             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4486             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4487         }
4488     } else { int r;
4489         r = boards[moveNum][CASTLING][0] = initialRights[0];
4490         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4491         r = boards[moveNum][CASTLING][1] = initialRights[1];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4493         r = boards[moveNum][CASTLING][3] = initialRights[3];
4494         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4495         r = boards[moveNum][CASTLING][4] = initialRights[4];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4497         /* wildcastle kludge: always assume King has rights */
4498         r = boards[moveNum][CASTLING][2] = initialRights[2];
4499         r = boards[moveNum][CASTLING][5] = initialRights[5];
4500     }
4501     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4502     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4503
4504
4505     if (ics_getting_history == H_GOT_REQ_HEADER ||
4506         ics_getting_history == H_GOT_UNREQ_HEADER) {
4507         /* This was an initial position from a move list, not
4508            the current position */
4509         return;
4510     }
4511
4512     /* Update currentMove and known move number limits */
4513     newMove = newGame || moveNum > forwardMostMove;
4514
4515     if (newGame) {
4516         forwardMostMove = backwardMostMove = currentMove = moveNum;
4517         if (gameMode == IcsExamining && moveNum == 0) {
4518           /* Workaround for ICS limitation: we are not told the wild
4519              type when starting to examine a game.  But if we ask for
4520              the move list, the move list header will tell us */
4521             ics_getting_history = H_REQUESTED;
4522             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4523             SendToICS(str);
4524         }
4525     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4526                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4527 #if ZIPPY
4528         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4529         /* [HGM] applied this also to an engine that is silently watching        */
4530         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4531             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4532             gameInfo.variant == currentlyInitializedVariant) {
4533           takeback = forwardMostMove - moveNum;
4534           for (i = 0; i < takeback; i++) {
4535             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4536             SendToProgram("undo\n", &first);
4537           }
4538         }
4539 #endif
4540
4541         forwardMostMove = moveNum;
4542         if (!pausing || currentMove > forwardMostMove)
4543           currentMove = forwardMostMove;
4544     } else {
4545         /* New part of history that is not contiguous with old part */
4546         if (pausing && gameMode == IcsExamining) {
4547             pauseExamInvalid = TRUE;
4548             forwardMostMove = pauseExamForwardMostMove;
4549             return;
4550         }
4551         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4552 #if ZIPPY
4553             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4554                 // [HGM] when we will receive the move list we now request, it will be
4555                 // fed to the engine from the first move on. So if the engine is not
4556                 // in the initial position now, bring it there.
4557                 InitChessProgram(&first, 0);
4558             }
4559 #endif
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564         forwardMostMove = backwardMostMove = currentMove = moveNum;
4565     }
4566
4567     /* Update the clocks */
4568     if (strchr(elapsed_time, '.')) {
4569       /* Time is in ms */
4570       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4571       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4572     } else {
4573       /* Time is in seconds */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4576     }
4577
4578
4579 #if ZIPPY
4580     if (appData.zippyPlay && newGame &&
4581         gameMode != IcsObserving && gameMode != IcsIdle &&
4582         gameMode != IcsExamining)
4583       ZippyFirstBoard(moveNum, basetime, increment);
4584 #endif
4585
4586     /* Put the move on the move list, first converting
4587        to canonical algebraic form. */
4588     if (moveNum > 0) {
4589   if (appData.debugMode) {
4590     if (appData.debugMode) { int f = forwardMostMove;
4591         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4592                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4593                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4594     }
4595     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4596     fprintf(debugFP, "moveNum = %d\n", moveNum);
4597     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4598     setbuf(debugFP, NULL);
4599   }
4600         if (moveNum <= backwardMostMove) {
4601             /* We don't know what the board looked like before
4602                this move.  Punt. */
4603           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4604             strcat(parseList[moveNum - 1], " ");
4605             strcat(parseList[moveNum - 1], elapsed_time);
4606             moveList[moveNum - 1][0] = NULLCHAR;
4607         } else if (strcmp(move_str, "none") == 0) {
4608             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4609             /* Again, we don't know what the board looked like;
4610                this is really the start of the game. */
4611             parseList[moveNum - 1][0] = NULLCHAR;
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613             backwardMostMove = moveNum;
4614             startedFromSetupPosition = TRUE;
4615             fromX = fromY = toX = toY = -1;
4616         } else {
4617           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4618           //                 So we parse the long-algebraic move string in stead of the SAN move
4619           int valid; char buf[MSG_SIZ], *prom;
4620
4621           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4622                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4623           // str looks something like "Q/a1-a2"; kill the slash
4624           if(str[1] == '/')
4625             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4626           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4627           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4628                 strcat(buf, prom); // long move lacks promo specification!
4629           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4630                 if(appData.debugMode)
4631                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4632                 safeStrCpy(move_str, buf, MSG_SIZ);
4633           }
4634           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4635                                 &fromX, &fromY, &toX, &toY, &promoChar)
4636                || ParseOneMove(buf, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar);
4638           // end of long SAN patch
4639           if (valid) {
4640             (void) CoordsToAlgebraic(boards[moveNum - 1],
4641                                      PosFlags(moveNum - 1),
4642                                      fromY, fromX, toY, toX, promoChar,
4643                                      parseList[moveNum-1]);
4644             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4645               case MT_NONE:
4646               case MT_STALEMATE:
4647               default:
4648                 break;
4649               case MT_CHECK:
4650                 if(gameInfo.variant != VariantShogi)
4651                     strcat(parseList[moveNum - 1], "+");
4652                 break;
4653               case MT_CHECKMATE:
4654               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4655                 strcat(parseList[moveNum - 1], "#");
4656                 break;
4657             }
4658             strcat(parseList[moveNum - 1], " ");
4659             strcat(parseList[moveNum - 1], elapsed_time);
4660             /* currentMoveString is set as a side-effect of ParseOneMove */
4661             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4662             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4663             strcat(moveList[moveNum - 1], "\n");
4664
4665             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4666                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4667               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4668                 ChessSquare old, new = boards[moveNum][k][j];
4669                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4670                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4671                   if(old == new) continue;
4672                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4673                   else if(new == WhiteWazir || new == BlackWazir) {
4674                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4675                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4676                       else boards[moveNum][k][j] = old; // preserve type of Gold
4677                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4678                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4679               }
4680           } else {
4681             /* Move from ICS was illegal!?  Punt. */
4682             if (appData.debugMode) {
4683               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4684               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4685             }
4686             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             moveList[moveNum - 1][0] = NULLCHAR;
4690             fromX = fromY = toX = toY = -1;
4691           }
4692         }
4693   if (appData.debugMode) {
4694     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4695     setbuf(debugFP, NULL);
4696   }
4697
4698 #if ZIPPY
4699         /* Send move to chess program (BEFORE animating it). */
4700         if (appData.zippyPlay && !newGame && newMove &&
4701            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4702
4703             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4704                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4705                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4707                             move_str);
4708                     DisplayError(str, 0);
4709                 } else {
4710                     if (first.sendTime) {
4711                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4712                     }
4713                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4714                     if (firstMove && !bookHit) {
4715                         firstMove = FALSE;
4716                         if (first.useColors) {
4717                           SendToProgram(gameMode == IcsPlayingWhite ?
4718                                         "white\ngo\n" :
4719                                         "black\ngo\n", &first);
4720                         } else {
4721                           SendToProgram("go\n", &first);
4722                         }
4723                         first.maybeThinking = TRUE;
4724                     }
4725                 }
4726             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4727               if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4729                 DisplayError(str, 0);
4730               } else {
4731                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4732                 SendMoveToProgram(moveNum - 1, &first);
4733               }
4734             }
4735         }
4736 #endif
4737     }
4738
4739     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4740         /* If move comes from a remote source, animate it.  If it
4741            isn't remote, it will have already been animated. */
4742         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4743             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4744         }
4745         if (!pausing && appData.highlightLastMove) {
4746             SetHighlights(fromX, fromY, toX, toY);
4747         }
4748     }
4749
4750     /* Start the clocks */
4751     whiteFlag = blackFlag = FALSE;
4752     appData.clockMode = !(basetime == 0 && increment == 0);
4753     if (ticking == 0) {
4754       ics_clock_paused = TRUE;
4755       StopClocks();
4756     } else if (ticking == 1) {
4757       ics_clock_paused = FALSE;
4758     }
4759     if (gameMode == IcsIdle ||
4760         relation == RELATION_OBSERVING_STATIC ||
4761         relation == RELATION_EXAMINING ||
4762         ics_clock_paused)
4763       DisplayBothClocks();
4764     else
4765       StartClocks();
4766
4767     /* Display opponents and material strengths */
4768     if (gameInfo.variant != VariantBughouse &&
4769         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4770         if (tinyLayout || smallLayout) {
4771             if(gameInfo.variant == VariantNormal)
4772               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4773                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4774                     basetime, increment);
4775             else
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment, (int) gameInfo.variant);
4779         } else {
4780             if(gameInfo.variant == VariantNormal)
4781               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4782                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4783                     basetime, increment);
4784             else
4785               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4786                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4787                     basetime, increment, VariantName(gameInfo.variant));
4788         }
4789         DisplayTitle(str);
4790   if (appData.debugMode) {
4791     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4792   }
4793     }
4794
4795
4796     /* Display the board */
4797     if (!pausing && !appData.noGUI) {
4798
4799       if (appData.premove)
4800           if (!gotPremove ||
4801              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4802              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4803               ClearPremoveHighlights();
4804
4805       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4806         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4807       DrawPosition(j, boards[currentMove]);
4808
4809       DisplayMove(moveNum - 1);
4810       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4811             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4812               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4813         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4814       }
4815     }
4816
4817     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4818 #if ZIPPY
4819     if(bookHit) { // [HGM] book: simulate book reply
4820         static char bookMove[MSG_SIZ]; // a bit generous?
4821
4822         programStats.nodes = programStats.depth = programStats.time =
4823         programStats.score = programStats.got_only_move = 0;
4824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4825
4826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4827         strcat(bookMove, bookHit);
4828         HandleMachineMove(bookMove, &first);
4829     }
4830 #endif
4831 }
4832
4833 void
4834 GetMoveListEvent()
4835 {
4836     char buf[MSG_SIZ];
4837     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4838         ics_getting_history = H_REQUESTED;
4839         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4840         SendToICS(buf);
4841     }
4842 }
4843
4844 void
4845 AnalysisPeriodicEvent(force)
4846      int force;
4847 {
4848     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4849          && !force) || !appData.periodicUpdates)
4850       return;
4851
4852     /* Send . command to Crafty to collect stats */
4853     SendToProgram(".\n", &first);
4854
4855     /* Don't send another until we get a response (this makes
4856        us stop sending to old Crafty's which don't understand
4857        the "." command (sending illegal cmds resets node count & time,
4858        which looks bad)) */
4859     programStats.ok_to_send = 0;
4860 }
4861
4862 void ics_update_width(new_width)
4863         int new_width;
4864 {
4865         ics_printf("set width %d\n", new_width);
4866 }
4867
4868 void
4869 SendMoveToProgram(moveNum, cps)
4870      int moveNum;
4871      ChessProgramState *cps;
4872 {
4873     char buf[MSG_SIZ];
4874
4875     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4876         // null move in variant where engine does not understand it (for analysis purposes)
4877         SendBoard(cps, moveNum + 1); // send position after move in stead.
4878         return;
4879     }
4880     if (cps->useUsermove) {
4881       SendToProgram("usermove ", cps);
4882     }
4883     if (cps->useSAN) {
4884       char *space;
4885       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4886         int len = space - parseList[moveNum];
4887         memcpy(buf, parseList[moveNum], len);
4888         buf[len++] = '\n';
4889         buf[len] = NULLCHAR;
4890       } else {
4891         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4892       }
4893       SendToProgram(buf, cps);
4894     } else {
4895       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4896         AlphaRank(moveList[moveNum], 4);
4897         SendToProgram(moveList[moveNum], cps);
4898         AlphaRank(moveList[moveNum], 4); // and back
4899       } else
4900       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4901        * the engine. It would be nice to have a better way to identify castle
4902        * moves here. */
4903       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4904                                                                          && cps->useOOCastle) {
4905         int fromX = moveList[moveNum][0] - AAA;
4906         int fromY = moveList[moveNum][1] - ONE;
4907         int toX = moveList[moveNum][2] - AAA;
4908         int toY = moveList[moveNum][3] - ONE;
4909         if((boards[moveNum][fromY][fromX] == WhiteKing
4910             && boards[moveNum][toY][toX] == WhiteRook)
4911            || (boards[moveNum][fromY][fromX] == BlackKing
4912                && boards[moveNum][toY][toX] == BlackRook)) {
4913           if(toX > fromX) SendToProgram("O-O\n", cps);
4914           else SendToProgram("O-O-O\n", cps);
4915         }
4916         else SendToProgram(moveList[moveNum], cps);
4917       } else
4918       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4919         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4920           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4921           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4922                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         } else
4924           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4925                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         SendToProgram(buf, cps);
4927       }
4928       else SendToProgram(moveList[moveNum], cps);
4929       /* End of additions by Tord */
4930     }
4931
4932     /* [HGM] setting up the opening has brought engine in force mode! */
4933     /*       Send 'go' if we are in a mode where machine should play. */
4934     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4935         (gameMode == TwoMachinesPlay   ||
4936 #if ZIPPY
4937          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4938 #endif
4939          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4940         SendToProgram("go\n", cps);
4941   if (appData.debugMode) {
4942     fprintf(debugFP, "(extra)\n");
4943   }
4944     }
4945     setboardSpoiledMachineBlack = 0;
4946 }
4947
4948 void
4949 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4950      ChessMove moveType;
4951      int fromX, fromY, toX, toY;
4952      char promoChar;
4953 {
4954     char user_move[MSG_SIZ];
4955     char suffix[4];
4956
4957     if(gameInfo.variant == VariantSChess && promoChar) {
4958         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4959         if(toX == BOARD_WIDTH>>1) moveType = WhitePromotion; // kludge to do gating at Rook
4960     } else suffix[0] = NULLCHAR;
4961
4962     switch (moveType) {
4963       default:
4964         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4965                 (int)moveType, fromX, fromY, toX, toY);
4966         DisplayError(user_move + strlen("say "), 0);
4967         break;
4968       case WhiteKingSideCastle:
4969       case BlackKingSideCastle:
4970       case WhiteQueenSideCastleWild:
4971       case BlackQueenSideCastleWild:
4972       /* PUSH Fabien */
4973       case WhiteHSideCastleFR:
4974       case BlackHSideCastleFR:
4975       /* POP Fabien */
4976         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4977         break;
4978       case WhiteQueenSideCastle:
4979       case BlackQueenSideCastle:
4980       case WhiteKingSideCastleWild:
4981       case BlackKingSideCastleWild:
4982       /* PUSH Fabien */
4983       case WhiteASideCastleFR:
4984       case BlackASideCastleFR:
4985       /* POP Fabien */
4986         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4987         break;
4988       case WhiteNonPromotion:
4989       case BlackNonPromotion:
4990         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4991         break;
4992       case WhitePromotion:
4993       case BlackPromotion:
4994         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4995           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4997                 PieceToChar(WhiteFerz));
4998         else if(gameInfo.variant == VariantGreat)
4999           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5000                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5001                 PieceToChar(WhiteMan));
5002         else
5003           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5004                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5005                 promoChar);
5006         break;
5007       case WhiteDrop:
5008       case BlackDrop:
5009       drop:
5010         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5011                  ToUpper(PieceToChar((ChessSquare) fromX)),
5012                  AAA + toX, ONE + toY);
5013         break;
5014       case IllegalMove:  /* could be a variant we don't quite understand */
5015         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5016       case NormalMove:
5017       case WhiteCapturesEnPassant:
5018       case BlackCapturesEnPassant:
5019         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5020                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5021         break;
5022     }
5023     SendToICS(user_move);
5024     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5025         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5026 }
5027
5028 void
5029 UploadGameEvent()
5030 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5031     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5032     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5033     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5034         DisplayError("You cannot do this while you are playing or observing", 0);
5035         return;
5036     }
5037     if(gameMode != IcsExamining) { // is this ever not the case?
5038         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5039
5040         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5041           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5042         } else { // on FICS we must first go to general examine mode
5043           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5044         }
5045         if(gameInfo.variant != VariantNormal) {
5046             // try figure out wild number, as xboard names are not always valid on ICS
5047             for(i=1; i<=36; i++) {
5048               snprintf(buf, MSG_SIZ, "wild/%d", i);
5049                 if(StringToVariant(buf) == gameInfo.variant) break;
5050             }
5051             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5052             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5053             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5054         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5055         SendToICS(ics_prefix);
5056         SendToICS(buf);
5057         if(startedFromSetupPosition || backwardMostMove != 0) {
5058           fen = PositionToFEN(backwardMostMove, NULL);
5059           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5060             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5061             SendToICS(buf);
5062           } else { // FICS: everything has to set by separate bsetup commands
5063             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5064             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5065             SendToICS(buf);
5066             if(!WhiteOnMove(backwardMostMove)) {
5067                 SendToICS("bsetup tomove black\n");
5068             }
5069             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5070             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5071             SendToICS(buf);
5072             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5073             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5074             SendToICS(buf);
5075             i = boards[backwardMostMove][EP_STATUS];
5076             if(i >= 0) { // set e.p.
5077               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5078                 SendToICS(buf);
5079             }
5080             bsetup++;
5081           }
5082         }
5083       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5084             SendToICS("bsetup done\n"); // switch to normal examining.
5085     }
5086     for(i = backwardMostMove; i<last; i++) {
5087         char buf[20];
5088         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5089         SendToICS(buf);
5090     }
5091     SendToICS(ics_prefix);
5092     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5093 }
5094
5095 void
5096 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5097      int rf, ff, rt, ft;
5098      char promoChar;
5099      char move[7];
5100 {
5101     if (rf == DROP_RANK) {
5102       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5103       sprintf(move, "%c@%c%c\n",
5104                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5105     } else {
5106         if (promoChar == 'x' || promoChar == NULLCHAR) {
5107           sprintf(move, "%c%c%c%c\n",
5108                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5109         } else {
5110             sprintf(move, "%c%c%c%c%c\n",
5111                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5112         }
5113     }
5114 }
5115
5116 void
5117 ProcessICSInitScript(f)
5118      FILE *f;
5119 {
5120     char buf[MSG_SIZ];
5121
5122     while (fgets(buf, MSG_SIZ, f)) {
5123         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5124     }
5125
5126     fclose(f);
5127 }
5128
5129
5130 static int lastX, lastY, selectFlag, dragging;
5131
5132 void
5133 Sweep(int step)
5134 {
5135     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5136     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5137     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5138     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5139     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5140     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5141     do {
5142         promoSweep -= step;
5143         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5144         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5145         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5146         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5147         if(!step) step = -1;
5148     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5149             appData.testLegality && (promoSweep == king ||
5150             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5151     ChangeDragPiece(promoSweep);
5152 }
5153
5154 int PromoScroll(int x, int y)
5155 {
5156   int step = 0;
5157
5158   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5159   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5160   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5161   if(!step) return FALSE;
5162   lastX = x; lastY = y;
5163   if((promoSweep < BlackPawn) == flipView) step = -step;
5164   if(step > 0) selectFlag = 1;
5165   if(!selectFlag) Sweep(step);
5166   return FALSE;
5167 }
5168
5169 void
5170 NextPiece(int step)
5171 {
5172     ChessSquare piece = boards[currentMove][toY][toX];
5173     do {
5174         pieceSweep -= step;
5175         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5176         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5177         if(!step) step = -1;
5178     } while(PieceToChar(pieceSweep) == '.');
5179     boards[currentMove][toY][toX] = pieceSweep;
5180     DrawPosition(FALSE, boards[currentMove]);
5181     boards[currentMove][toY][toX] = piece;
5182 }
5183 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5184 void
5185 AlphaRank(char *move, int n)
5186 {
5187 //    char *p = move, c; int x, y;
5188
5189     if (appData.debugMode) {
5190         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5191     }
5192
5193     if(move[1]=='*' &&
5194        move[2]>='0' && move[2]<='9' &&
5195        move[3]>='a' && move[3]<='x'    ) {
5196         move[1] = '@';
5197         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5199     } else
5200     if(move[0]>='0' && move[0]<='9' &&
5201        move[1]>='a' && move[1]<='x' &&
5202        move[2]>='0' && move[2]<='9' &&
5203        move[3]>='a' && move[3]<='x'    ) {
5204         /* input move, Shogi -> normal */
5205         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5206         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5207         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5209     } else
5210     if(move[1]=='@' &&
5211        move[3]>='0' && move[3]<='9' &&
5212        move[2]>='a' && move[2]<='x'    ) {
5213         move[1] = '*';
5214         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5215         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5216     } else
5217     if(
5218        move[0]>='a' && move[0]<='x' &&
5219        move[3]>='0' && move[3]<='9' &&
5220        move[2]>='a' && move[2]<='x'    ) {
5221          /* output move, normal -> Shogi */
5222         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5223         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5224         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5225         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5226         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5227     }
5228     if (appData.debugMode) {
5229         fprintf(debugFP, "   out = '%s'\n", move);
5230     }
5231 }
5232
5233 char yy_textstr[8000];
5234
5235 /* Parser for moves from gnuchess, ICS, or user typein box */
5236 Boolean
5237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5238      char *move;
5239      int moveNum;
5240      ChessMove *moveType;
5241      int *fromX, *fromY, *toX, *toY;
5242      char *promoChar;
5243 {
5244     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5245
5246     switch (*moveType) {
5247       case WhitePromotion:
5248       case BlackPromotion:
5249       case WhiteNonPromotion:
5250       case BlackNonPromotion:
5251       case NormalMove:
5252       case WhiteCapturesEnPassant:
5253       case BlackCapturesEnPassant:
5254       case WhiteKingSideCastle:
5255       case WhiteQueenSideCastle:
5256       case BlackKingSideCastle:
5257       case BlackQueenSideCastle:
5258       case WhiteKingSideCastleWild:
5259       case WhiteQueenSideCastleWild:
5260       case BlackKingSideCastleWild:
5261       case BlackQueenSideCastleWild:
5262       /* Code added by Tord: */
5263       case WhiteHSideCastleFR:
5264       case WhiteASideCastleFR:
5265       case BlackHSideCastleFR:
5266       case BlackASideCastleFR:
5267       /* End of code added by Tord */
5268       case IllegalMove:         /* bug or odd chess variant */
5269         *fromX = currentMoveString[0] - AAA;
5270         *fromY = currentMoveString[1] - ONE;
5271         *toX = currentMoveString[2] - AAA;
5272         *toY = currentMoveString[3] - ONE;
5273         *promoChar = currentMoveString[4];
5274         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5275             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5276     if (appData.debugMode) {
5277         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5278     }
5279             *fromX = *fromY = *toX = *toY = 0;
5280             return FALSE;
5281         }
5282         if (appData.testLegality) {
5283           return (*moveType != IllegalMove);
5284         } else {
5285           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5286                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5287         }
5288
5289       case WhiteDrop:
5290       case BlackDrop:
5291         *fromX = *moveType == WhiteDrop ?
5292           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5293           (int) CharToPiece(ToLower(currentMoveString[0]));
5294         *fromY = DROP_RANK;
5295         *toX = currentMoveString[2] - AAA;
5296         *toY = currentMoveString[3] - ONE;
5297         *promoChar = NULLCHAR;
5298         return TRUE;
5299
5300       case AmbiguousMove:
5301       case ImpossibleMove:
5302       case EndOfFile:
5303       case ElapsedTime:
5304       case Comment:
5305       case PGNTag:
5306       case NAG:
5307       case WhiteWins:
5308       case BlackWins:
5309       case GameIsDrawn:
5310       default:
5311     if (appData.debugMode) {
5312         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5313     }
5314         /* bug? */
5315         *fromX = *fromY = *toX = *toY = 0;
5316         *promoChar = NULLCHAR;
5317         return FALSE;
5318     }
5319 }
5320
5321 Boolean pushed = FALSE;
5322 char *lastParseAttempt;
5323
5324 void
5325 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5326 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5327   int fromX, fromY, toX, toY; char promoChar;
5328   ChessMove moveType;
5329   Boolean valid;
5330   int nr = 0;
5331
5332   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5333     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5334     pushed = TRUE;
5335   }
5336   endPV = forwardMostMove;
5337   do {
5338     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5339     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5340     lastParseAttempt = pv;
5341     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5342 if(appData.debugMode){
5343 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);
5344 }
5345     if(!valid && nr == 0 &&
5346        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5347         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5348         // Hande case where played move is different from leading PV move
5349         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5350         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5351         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5352         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5353           endPV += 2; // if position different, keep this
5354           moveList[endPV-1][0] = fromX + AAA;
5355           moveList[endPV-1][1] = fromY + ONE;
5356           moveList[endPV-1][2] = toX + AAA;
5357           moveList[endPV-1][3] = toY + ONE;
5358           parseList[endPV-1][0] = NULLCHAR;
5359           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5360         }
5361       }
5362     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5363     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5364     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5365     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5366         valid++; // allow comments in PV
5367         continue;
5368     }
5369     nr++;
5370     if(endPV+1 > framePtr) break; // no space, truncate
5371     if(!valid) break;
5372     endPV++;
5373     CopyBoard(boards[endPV], boards[endPV-1]);
5374     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5375     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5376     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5377     CoordsToAlgebraic(boards[endPV - 1],
5378                              PosFlags(endPV - 1),
5379                              fromY, fromX, toY, toX, promoChar,
5380                              parseList[endPV - 1]);
5381   } while(valid);
5382   if(atEnd == 2) return; // used hidden, for PV conversion
5383   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5384   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5385   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5386                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5387   DrawPosition(TRUE, boards[currentMove]);
5388 }
5389
5390 int
5391 MultiPV(ChessProgramState *cps)
5392 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5393         int i;
5394         for(i=0; i<cps->nrOptions; i++)
5395             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5396                 return i;
5397         return -1;
5398 }
5399
5400 Boolean
5401 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5402 {
5403         int startPV, multi, lineStart, origIndex = index;
5404         char *p, buf2[MSG_SIZ];
5405
5406         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5407         lastX = x; lastY = y;
5408         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5409         lineStart = startPV = index;
5410         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5411         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5412         index = startPV;
5413         do{ while(buf[index] && buf[index] != '\n') index++;
5414         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5415         buf[index] = 0;
5416         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5417                 int n = first.option[multi].value;
5418                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5419                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5420                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5421                 first.option[multi].value = n;
5422                 *start = *end = 0;
5423                 return FALSE;
5424         }
5425         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5426         *start = startPV; *end = index-1;
5427         return TRUE;
5428 }
5429
5430 char *
5431 PvToSAN(char *pv)
5432 {
5433         static char buf[10*MSG_SIZ];
5434         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5435         *buf = NULLCHAR;
5436         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5437         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5438         for(i = forwardMostMove; i<endPV; i++){
5439             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5440             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5441             k += strlen(buf+k);
5442         }
5443         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5444         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5445         endPV = savedEnd;
5446         return buf;
5447 }
5448
5449 Boolean
5450 LoadPV(int x, int y)
5451 { // called on right mouse click to load PV
5452   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5453   lastX = x; lastY = y;
5454   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5455   return TRUE;
5456 }
5457
5458 void
5459 UnLoadPV()
5460 {
5461   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5462   if(endPV < 0) return;
5463   endPV = -1;
5464   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5465         Boolean saveAnimate = appData.animate;
5466         if(pushed) {
5467             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5468                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5469             } else storedGames--; // abandon shelved tail of original game
5470         }
5471         pushed = FALSE;
5472         forwardMostMove = currentMove;
5473         currentMove = oldFMM;
5474         appData.animate = FALSE;
5475         ToNrEvent(forwardMostMove);
5476         appData.animate = saveAnimate;
5477   }
5478   currentMove = forwardMostMove;
5479   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5480   ClearPremoveHighlights();
5481   DrawPosition(TRUE, boards[currentMove]);
5482 }
5483
5484 void
5485 MovePV(int x, int y, int h)
5486 { // step through PV based on mouse coordinates (called on mouse move)
5487   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5488
5489   // we must somehow check if right button is still down (might be released off board!)
5490   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5491   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5492   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5493   if(!step) return;
5494   lastX = x; lastY = y;
5495
5496   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5497   if(endPV < 0) return;
5498   if(y < margin) step = 1; else
5499   if(y > h - margin) step = -1;
5500   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5501   currentMove += step;
5502   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5503   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5504                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5505   DrawPosition(FALSE, boards[currentMove]);
5506 }
5507
5508
5509 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5510 // All positions will have equal probability, but the current method will not provide a unique
5511 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5512 #define DARK 1
5513 #define LITE 2
5514 #define ANY 3
5515
5516 int squaresLeft[4];
5517 int piecesLeft[(int)BlackPawn];
5518 int seed, nrOfShuffles;
5519
5520 void GetPositionNumber()
5521 {       // sets global variable seed
5522         int i;
5523
5524         seed = appData.defaultFrcPosition;
5525         if(seed < 0) { // randomize based on time for negative FRC position numbers
5526                 for(i=0; i<50; i++) seed += random();
5527                 seed = random() ^ random() >> 8 ^ random() << 8;
5528                 if(seed<0) seed = -seed;
5529         }
5530 }
5531
5532 int put(Board board, int pieceType, int rank, int n, int shade)
5533 // put the piece on the (n-1)-th empty squares of the given shade
5534 {
5535         int i;
5536
5537         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5538                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5539                         board[rank][i] = (ChessSquare) pieceType;
5540                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5541                         squaresLeft[ANY]--;
5542                         piecesLeft[pieceType]--;
5543                         return i;
5544                 }
5545         }
5546         return -1;
5547 }
5548
5549
5550 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5551 // calculate where the next piece goes, (any empty square), and put it there
5552 {
5553         int i;
5554
5555         i = seed % squaresLeft[shade];
5556         nrOfShuffles *= squaresLeft[shade];
5557         seed /= squaresLeft[shade];
5558         put(board, pieceType, rank, i, shade);
5559 }
5560
5561 void AddTwoPieces(Board board, int pieceType, int rank)
5562 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5563 {
5564         int i, n=squaresLeft[ANY], j=n-1, k;
5565
5566         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5567         i = seed % k;  // pick one
5568         nrOfShuffles *= k;
5569         seed /= k;
5570         while(i >= j) i -= j--;
5571         j = n - 1 - j; i += j;
5572         put(board, pieceType, rank, j, ANY);
5573         put(board, pieceType, rank, i, ANY);
5574 }
5575
5576 void SetUpShuffle(Board board, int number)
5577 {
5578         int i, p, first=1;
5579
5580         GetPositionNumber(); nrOfShuffles = 1;
5581
5582         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5583         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5584         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5585
5586         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5587
5588         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5589             p = (int) board[0][i];
5590             if(p < (int) BlackPawn) piecesLeft[p] ++;
5591             board[0][i] = EmptySquare;
5592         }
5593
5594         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5595             // shuffles restricted to allow normal castling put KRR first
5596             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5597                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5598             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5599                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5600             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5601                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5602             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5603                 put(board, WhiteRook, 0, 0, ANY);
5604             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5605         }
5606
5607         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5608             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5609             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5610                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5611                 while(piecesLeft[p] >= 2) {
5612                     AddOnePiece(board, p, 0, LITE);
5613                     AddOnePiece(board, p, 0, DARK);
5614                 }
5615                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5616             }
5617
5618         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5619             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5620             // but we leave King and Rooks for last, to possibly obey FRC restriction
5621             if(p == (int)WhiteRook) continue;
5622             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5623             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5624         }
5625
5626         // now everything is placed, except perhaps King (Unicorn) and Rooks
5627
5628         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5629             // Last King gets castling rights
5630             while(piecesLeft[(int)WhiteUnicorn]) {
5631                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5632                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5633             }
5634
5635             while(piecesLeft[(int)WhiteKing]) {
5636                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5637                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5638             }
5639
5640
5641         } else {
5642             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5643             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5644         }
5645
5646         // Only Rooks can be left; simply place them all
5647         while(piecesLeft[(int)WhiteRook]) {
5648                 i = put(board, WhiteRook, 0, 0, ANY);
5649                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5650                         if(first) {
5651                                 first=0;
5652                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5653                         }
5654                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5655                 }
5656         }
5657         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5658             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5659         }
5660
5661         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5662 }
5663
5664 int SetCharTable( char *table, const char * map )
5665 /* [HGM] moved here from winboard.c because of its general usefulness */
5666 /*       Basically a safe strcpy that uses the last character as King */
5667 {
5668     int result = FALSE; int NrPieces;
5669
5670     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5671                     && NrPieces >= 12 && !(NrPieces&1)) {
5672         int i; /* [HGM] Accept even length from 12 to 34 */
5673
5674         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5675         for( i=0; i<NrPieces/2-1; i++ ) {
5676             table[i] = map[i];
5677             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5678         }
5679         table[(int) WhiteKing]  = map[NrPieces/2-1];
5680         table[(int) BlackKing]  = map[NrPieces-1];
5681
5682         result = TRUE;
5683     }
5684
5685     return result;
5686 }
5687
5688 void Prelude(Board board)
5689 {       // [HGM] superchess: random selection of exo-pieces
5690         int i, j, k; ChessSquare p;
5691         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5692
5693         GetPositionNumber(); // use FRC position number
5694
5695         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5696             SetCharTable(pieceToChar, appData.pieceToCharTable);
5697             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5698                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5699         }
5700
5701         j = seed%4;                 seed /= 4;
5702         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5703         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5704         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5705         j = seed%3 + (seed%3 >= j); seed /= 3;
5706         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5707         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5708         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5709         j = seed%3;                 seed /= 3;
5710         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5711         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5712         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5713         j = seed%2 + (seed%2 >= j); seed /= 2;
5714         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5715         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5716         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5717         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5718         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5719         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5720         put(board, exoPieces[0],    0, 0, ANY);
5721         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5722 }
5723
5724 void
5725 InitPosition(redraw)
5726      int redraw;
5727 {
5728     ChessSquare (* pieces)[BOARD_FILES];
5729     int i, j, pawnRow, overrule,
5730     oldx = gameInfo.boardWidth,
5731     oldy = gameInfo.boardHeight,
5732     oldh = gameInfo.holdingsWidth;
5733     static int oldv;
5734
5735     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5736
5737     /* [AS] Initialize pv info list [HGM] and game status */
5738     {
5739         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5740             pvInfoList[i].depth = 0;
5741             boards[i][EP_STATUS] = EP_NONE;
5742             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5743         }
5744
5745         initialRulePlies = 0; /* 50-move counter start */
5746
5747         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5748         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5749     }
5750
5751
5752     /* [HGM] logic here is completely changed. In stead of full positions */
5753     /* the initialized data only consist of the two backranks. The switch */
5754     /* selects which one we will use, which is than copied to the Board   */
5755     /* initialPosition, which for the rest is initialized by Pawns and    */
5756     /* empty squares. This initial position is then copied to boards[0],  */
5757     /* possibly after shuffling, so that it remains available.            */
5758
5759     gameInfo.holdingsWidth = 0; /* default board sizes */
5760     gameInfo.boardWidth    = 8;
5761     gameInfo.boardHeight   = 8;
5762     gameInfo.holdingsSize  = 0;
5763     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5764     for(i=0; i<BOARD_FILES-2; i++)
5765       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5766     initialPosition[EP_STATUS] = EP_NONE;
5767     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5768     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5769          SetCharTable(pieceNickName, appData.pieceNickNames);
5770     else SetCharTable(pieceNickName, "............");
5771     pieces = FIDEArray;
5772
5773     switch (gameInfo.variant) {
5774     case VariantFischeRandom:
5775       shuffleOpenings = TRUE;
5776     default:
5777       break;
5778     case VariantShatranj:
5779       pieces = ShatranjArray;
5780       nrCastlingRights = 0;
5781       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5782       break;
5783     case VariantMakruk:
5784       pieces = makrukArray;
5785       nrCastlingRights = 0;
5786       startedFromSetupPosition = TRUE;
5787       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5788       break;
5789     case VariantTwoKings:
5790       pieces = twoKingsArray;
5791       break;
5792     case VariantGrand:
5793       pieces = GrandArray;
5794       nrCastlingRights = 0;
5795       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5796       gameInfo.boardWidth = 10;
5797       gameInfo.boardHeight = 10;
5798       gameInfo.holdingsSize = 7;
5799       break;
5800     case VariantCapaRandom:
5801       shuffleOpenings = TRUE;
5802     case VariantCapablanca:
5803       pieces = CapablancaArray;
5804       gameInfo.boardWidth = 10;
5805       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5806       break;
5807     case VariantGothic:
5808       pieces = GothicArray;
5809       gameInfo.boardWidth = 10;
5810       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5811       break;
5812     case VariantSChess:
5813       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5814       gameInfo.holdingsSize = 7;
5815       break;
5816     case VariantJanus:
5817       pieces = JanusArray;
5818       gameInfo.boardWidth = 10;
5819       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5820       nrCastlingRights = 6;
5821         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5822         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5823         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5824         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5825         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5826         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5827       break;
5828     case VariantFalcon:
5829       pieces = FalconArray;
5830       gameInfo.boardWidth = 10;
5831       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5832       break;
5833     case VariantXiangqi:
5834       pieces = XiangqiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 10;
5837       nrCastlingRights = 0;
5838       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5839       break;
5840     case VariantShogi:
5841       pieces = ShogiArray;
5842       gameInfo.boardWidth  = 9;
5843       gameInfo.boardHeight = 9;
5844       gameInfo.holdingsSize = 7;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5847       break;
5848     case VariantCourier:
5849       pieces = CourierArray;
5850       gameInfo.boardWidth  = 12;
5851       nrCastlingRights = 0;
5852       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5853       break;
5854     case VariantKnightmate:
5855       pieces = KnightmateArray;
5856       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5857       break;
5858     case VariantSpartan:
5859       pieces = SpartanArray;
5860       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5861       break;
5862     case VariantFairy:
5863       pieces = fairyArray;
5864       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5865       break;
5866     case VariantGreat:
5867       pieces = GreatArray;
5868       gameInfo.boardWidth = 10;
5869       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5870       gameInfo.holdingsSize = 8;
5871       break;
5872     case VariantSuper:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5875       gameInfo.holdingsSize = 8;
5876       startedFromSetupPosition = TRUE;
5877       break;
5878     case VariantCrazyhouse:
5879     case VariantBughouse:
5880       pieces = FIDEArray;
5881       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5882       gameInfo.holdingsSize = 5;
5883       break;
5884     case VariantWildCastle:
5885       pieces = FIDEArray;
5886       /* !!?shuffle with kings guaranteed to be on d or e file */
5887       shuffleOpenings = 1;
5888       break;
5889     case VariantNoCastle:
5890       pieces = FIDEArray;
5891       nrCastlingRights = 0;
5892       /* !!?unconstrained back-rank shuffle */
5893       shuffleOpenings = 1;
5894       break;
5895     }
5896
5897     overrule = 0;
5898     if(appData.NrFiles >= 0) {
5899         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5900         gameInfo.boardWidth = appData.NrFiles;
5901     }
5902     if(appData.NrRanks >= 0) {
5903         gameInfo.boardHeight = appData.NrRanks;
5904     }
5905     if(appData.holdingsSize >= 0) {
5906         i = appData.holdingsSize;
5907         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5908         gameInfo.holdingsSize = i;
5909     }
5910     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5911     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5912         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5913
5914     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5915     if(pawnRow < 1) pawnRow = 1;
5916     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5917
5918     /* User pieceToChar list overrules defaults */
5919     if(appData.pieceToCharTable != NULL)
5920         SetCharTable(pieceToChar, appData.pieceToCharTable);
5921
5922     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5923
5924         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5925             s = (ChessSquare) 0; /* account holding counts in guard band */
5926         for( i=0; i<BOARD_HEIGHT; i++ )
5927             initialPosition[i][j] = s;
5928
5929         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5930         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5931         initialPosition[pawnRow][j] = WhitePawn;
5932         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5933         if(gameInfo.variant == VariantXiangqi) {
5934             if(j&1) {
5935                 initialPosition[pawnRow][j] =
5936                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5937                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5938                    initialPosition[2][j] = WhiteCannon;
5939                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5940                 }
5941             }
5942         }
5943         if(gameInfo.variant == VariantGrand) {
5944             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5945                initialPosition[0][j] = WhiteRook;
5946                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5947             }
5948         }
5949         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5950     }
5951     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5952
5953             j=BOARD_LEFT+1;
5954             initialPosition[1][j] = WhiteBishop;
5955             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5956             j=BOARD_RGHT-2;
5957             initialPosition[1][j] = WhiteRook;
5958             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5959     }
5960
5961     if( nrCastlingRights == -1) {
5962         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5963         /*       This sets default castling rights from none to normal corners   */
5964         /* Variants with other castling rights must set them themselves above    */
5965         nrCastlingRights = 6;
5966
5967         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5968         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5969         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5970         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5971         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5972         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5973      }
5974
5975      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5976      if(gameInfo.variant == VariantGreat) { // promotion commoners
5977         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5978         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5979         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5980         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5981      }
5982      if( gameInfo.variant == VariantSChess ) {
5983       initialPosition[1][0] = BlackMarshall;
5984       initialPosition[2][0] = BlackAngel;
5985       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5986       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5987       initialPosition[1][1] = initialPosition[2][1] = 
5988       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5989      }
5990   if (appData.debugMode) {
5991     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5992   }
5993     if(shuffleOpenings) {
5994         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5995         startedFromSetupPosition = TRUE;
5996     }
5997     if(startedFromPositionFile) {
5998       /* [HGM] loadPos: use PositionFile for every new game */
5999       CopyBoard(initialPosition, filePosition);
6000       for(i=0; i<nrCastlingRights; i++)
6001           initialRights[i] = filePosition[CASTLING][i];
6002       startedFromSetupPosition = TRUE;
6003     }
6004
6005     CopyBoard(boards[0], initialPosition);
6006
6007     if(oldx != gameInfo.boardWidth ||
6008        oldy != gameInfo.boardHeight ||
6009        oldv != gameInfo.variant ||
6010        oldh != gameInfo.holdingsWidth
6011                                          )
6012             InitDrawingSizes(-2 ,0);
6013
6014     oldv = gameInfo.variant;
6015     if (redraw)
6016       DrawPosition(TRUE, boards[currentMove]);
6017 }
6018
6019 void
6020 SendBoard(cps, moveNum)
6021      ChessProgramState *cps;
6022      int moveNum;
6023 {
6024     char message[MSG_SIZ];
6025
6026     if (cps->useSetboard) {
6027       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6028       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6029       SendToProgram(message, cps);
6030       free(fen);
6031
6032     } else {
6033       ChessSquare *bp;
6034       int i, j;
6035       /* Kludge to set black to move, avoiding the troublesome and now
6036        * deprecated "black" command.
6037        */
6038       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6039         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6040
6041       SendToProgram("edit\n", cps);
6042       SendToProgram("#\n", cps);
6043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6044         bp = &boards[moveNum][i][BOARD_LEFT];
6045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6046           if ((int) *bp < (int) BlackPawn) {
6047             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6048                     AAA + j, ONE + i);
6049             if(message[0] == '+' || message[0] == '~') {
6050               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6052                         AAA + j, ONE + i);
6053             }
6054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6055                 message[1] = BOARD_RGHT   - 1 - j + '1';
6056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6057             }
6058             SendToProgram(message, cps);
6059           }
6060         }
6061       }
6062
6063       SendToProgram("c\n", cps);
6064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6065         bp = &boards[moveNum][i][BOARD_LEFT];
6066         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6067           if (((int) *bp != (int) EmptySquare)
6068               && ((int) *bp >= (int) BlackPawn)) {
6069             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6070                     AAA + j, ONE + i);
6071             if(message[0] == '+' || message[0] == '~') {
6072               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6074                         AAA + j, ONE + i);
6075             }
6076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6077                 message[1] = BOARD_RGHT   - 1 - j + '1';
6078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6079             }
6080             SendToProgram(message, cps);
6081           }
6082         }
6083       }
6084
6085       SendToProgram(".\n", cps);
6086     }
6087     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6088 }
6089
6090 ChessSquare
6091 DefaultPromoChoice(int white)
6092 {
6093     ChessSquare result;
6094     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6095         result = WhiteFerz; // no choice
6096     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6097         result= WhiteKing; // in Suicide Q is the last thing we want
6098     else if(gameInfo.variant == VariantSpartan)
6099         result = white ? WhiteQueen : WhiteAngel;
6100     else result = WhiteQueen;
6101     if(!white) result = WHITE_TO_BLACK result;
6102     return result;
6103 }
6104
6105 static int autoQueen; // [HGM] oneclick
6106
6107 int
6108 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6109 {
6110     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6111     /* [HGM] add Shogi promotions */
6112     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6113     ChessSquare piece;
6114     ChessMove moveType;
6115     Boolean premove;
6116
6117     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6118     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6119
6120     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6121       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6122         return FALSE;
6123
6124     piece = boards[currentMove][fromY][fromX];
6125     if(gameInfo.variant == VariantShogi) {
6126         promotionZoneSize = BOARD_HEIGHT/3;
6127         highestPromotingPiece = (int)WhiteFerz;
6128     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6129         promotionZoneSize = 3;
6130     }
6131
6132     // Treat Lance as Pawn when it is not representing Amazon
6133     if(gameInfo.variant != VariantSuper) {
6134         if(piece == WhiteLance) piece = WhitePawn; else
6135         if(piece == BlackLance) piece = BlackPawn;
6136     }
6137
6138     // next weed out all moves that do not touch the promotion zone at all
6139     if((int)piece >= BlackPawn) {
6140         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6141              return FALSE;
6142         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6143     } else {
6144         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6145            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6146     }
6147
6148     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6149
6150     // weed out mandatory Shogi promotions
6151     if(gameInfo.variant == VariantShogi) {
6152         if(piece >= BlackPawn) {
6153             if(toY == 0 && piece == BlackPawn ||
6154                toY == 0 && piece == BlackQueen ||
6155                toY <= 1 && piece == BlackKnight) {
6156                 *promoChoice = '+';
6157                 return FALSE;
6158             }
6159         } else {
6160             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6161                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6162                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6163                 *promoChoice = '+';
6164                 return FALSE;
6165             }
6166         }
6167     }
6168
6169     // weed out obviously illegal Pawn moves
6170     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6171         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6172         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6173         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6174         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6175         // note we are not allowed to test for valid (non-)capture, due to premove
6176     }
6177
6178     // we either have a choice what to promote to, or (in Shogi) whether to promote
6179     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6180         *promoChoice = PieceToChar(BlackFerz);  // no choice
6181         return FALSE;
6182     }
6183     // no sense asking what we must promote to if it is going to explode...
6184     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6185         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6186         return FALSE;
6187     }
6188     // give caller the default choice even if we will not make it
6189     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6190     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6191     if(        sweepSelect && gameInfo.variant != VariantGreat
6192                            && gameInfo.variant != VariantGrand
6193                            && gameInfo.variant != VariantSuper) return FALSE;
6194     if(autoQueen) return FALSE; // predetermined
6195
6196     // suppress promotion popup on illegal moves that are not premoves
6197     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6198               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6199     if(appData.testLegality && !premove) {
6200         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6201                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6202         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6203             return FALSE;
6204     }
6205
6206     return TRUE;
6207 }
6208
6209 int
6210 InPalace(row, column)
6211      int row, column;
6212 {   /* [HGM] for Xiangqi */
6213     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6214          column < (BOARD_WIDTH + 4)/2 &&
6215          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6216     return FALSE;
6217 }
6218
6219 int
6220 PieceForSquare (x, y)
6221      int x;
6222      int y;
6223 {
6224   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6225      return -1;
6226   else
6227      return boards[currentMove][y][x];
6228 }
6229
6230 int
6231 OKToStartUserMove(x, y)
6232      int x, y;
6233 {
6234     ChessSquare from_piece;
6235     int white_piece;
6236
6237     if (matchMode) return FALSE;
6238     if (gameMode == EditPosition) return TRUE;
6239
6240     if (x >= 0 && y >= 0)
6241       from_piece = boards[currentMove][y][x];
6242     else
6243       from_piece = EmptySquare;
6244
6245     if (from_piece == EmptySquare) return FALSE;
6246
6247     white_piece = (int)from_piece >= (int)WhitePawn &&
6248       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6249
6250     switch (gameMode) {
6251       case AnalyzeFile:
6252       case TwoMachinesPlay:
6253       case EndOfGame:
6254         return FALSE;
6255
6256       case IcsObserving:
6257       case IcsIdle:
6258         return FALSE;
6259
6260       case MachinePlaysWhite:
6261       case IcsPlayingBlack:
6262         if (appData.zippyPlay) return FALSE;
6263         if (white_piece) {
6264             DisplayMoveError(_("You are playing Black"));
6265             return FALSE;
6266         }
6267         break;
6268
6269       case MachinePlaysBlack:
6270       case IcsPlayingWhite:
6271         if (appData.zippyPlay) return FALSE;
6272         if (!white_piece) {
6273             DisplayMoveError(_("You are playing White"));
6274             return FALSE;
6275         }
6276         break;
6277
6278       case PlayFromGameFile:
6279             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6280       case EditGame:
6281         if (!white_piece && WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is White's turn"));
6283             return FALSE;
6284         }
6285         if (white_piece && !WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is Black's turn"));
6287             return FALSE;
6288         }
6289         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6290             /* Editing correspondence game history */
6291             /* Could disallow this or prompt for confirmation */
6292             cmailOldMove = -1;
6293         }
6294         break;
6295
6296       case BeginningOfGame:
6297         if (appData.icsActive) return FALSE;
6298         if (!appData.noChessProgram) {
6299             if (!white_piece) {
6300                 DisplayMoveError(_("You are playing White"));
6301                 return FALSE;
6302             }
6303         }
6304         break;
6305
6306       case Training:
6307         if (!white_piece && WhiteOnMove(currentMove)) {
6308             DisplayMoveError(_("It is White's turn"));
6309             return FALSE;
6310         }
6311         if (white_piece && !WhiteOnMove(currentMove)) {
6312             DisplayMoveError(_("It is Black's turn"));
6313             return FALSE;
6314         }
6315         break;
6316
6317       default:
6318       case IcsExamining:
6319         break;
6320     }
6321     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6322         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6323         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6324         && gameMode != AnalyzeFile && gameMode != Training) {
6325         DisplayMoveError(_("Displayed position is not current"));
6326         return FALSE;
6327     }
6328     return TRUE;
6329 }
6330
6331 Boolean
6332 OnlyMove(int *x, int *y, Boolean captures) {
6333     DisambiguateClosure cl;
6334     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6335     switch(gameMode) {
6336       case MachinePlaysBlack:
6337       case IcsPlayingWhite:
6338       case BeginningOfGame:
6339         if(!WhiteOnMove(currentMove)) return FALSE;
6340         break;
6341       case MachinePlaysWhite:
6342       case IcsPlayingBlack:
6343         if(WhiteOnMove(currentMove)) return FALSE;
6344         break;
6345       case EditGame:
6346         break;
6347       default:
6348         return FALSE;
6349     }
6350     cl.pieceIn = EmptySquare;
6351     cl.rfIn = *y;
6352     cl.ffIn = *x;
6353     cl.rtIn = -1;
6354     cl.ftIn = -1;
6355     cl.promoCharIn = NULLCHAR;
6356     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6357     if( cl.kind == NormalMove ||
6358         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6359         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6360         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6361       fromX = cl.ff;
6362       fromY = cl.rf;
6363       *x = cl.ft;
6364       *y = cl.rt;
6365       return TRUE;
6366     }
6367     if(cl.kind != ImpossibleMove) return FALSE;
6368     cl.pieceIn = EmptySquare;
6369     cl.rfIn = -1;
6370     cl.ffIn = -1;
6371     cl.rtIn = *y;
6372     cl.ftIn = *x;
6373     cl.promoCharIn = NULLCHAR;
6374     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6375     if( cl.kind == NormalMove ||
6376         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6377         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6378         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6379       fromX = cl.ff;
6380       fromY = cl.rf;
6381       *x = cl.ft;
6382       *y = cl.rt;
6383       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6384       return TRUE;
6385     }
6386     return FALSE;
6387 }
6388
6389 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6390 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6391 int lastLoadGameUseList = FALSE;
6392 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6393 ChessMove lastLoadGameStart = EndOfFile;
6394
6395 void
6396 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6397      int fromX, fromY, toX, toY;
6398      int promoChar;
6399 {
6400     ChessMove moveType;
6401     ChessSquare pdown, pup;
6402
6403     /* Check if the user is playing in turn.  This is complicated because we
6404        let the user "pick up" a piece before it is his turn.  So the piece he
6405        tried to pick up may have been captured by the time he puts it down!
6406        Therefore we use the color the user is supposed to be playing in this
6407        test, not the color of the piece that is currently on the starting
6408        square---except in EditGame mode, where the user is playing both
6409        sides; fortunately there the capture race can't happen.  (It can
6410        now happen in IcsExamining mode, but that's just too bad.  The user
6411        will get a somewhat confusing message in that case.)
6412        */
6413
6414     switch (gameMode) {
6415       case AnalyzeFile:
6416       case TwoMachinesPlay:
6417       case EndOfGame:
6418       case IcsObserving:
6419       case IcsIdle:
6420         /* We switched into a game mode where moves are not accepted,
6421            perhaps while the mouse button was down. */
6422         return;
6423
6424       case MachinePlaysWhite:
6425         /* User is moving for Black */
6426         if (WhiteOnMove(currentMove)) {
6427             DisplayMoveError(_("It is White's turn"));
6428             return;
6429         }
6430         break;
6431
6432       case MachinePlaysBlack:
6433         /* User is moving for White */
6434         if (!WhiteOnMove(currentMove)) {
6435             DisplayMoveError(_("It is Black's turn"));
6436             return;
6437         }
6438         break;
6439
6440       case PlayFromGameFile:
6441             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6442       case EditGame:
6443       case IcsExamining:
6444       case BeginningOfGame:
6445       case AnalyzeMode:
6446       case Training:
6447         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6448         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6449             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6450             /* User is moving for Black */
6451             if (WhiteOnMove(currentMove)) {
6452                 DisplayMoveError(_("It is White's turn"));
6453                 return;
6454             }
6455         } else {
6456             /* User is moving for White */
6457             if (!WhiteOnMove(currentMove)) {
6458                 DisplayMoveError(_("It is Black's turn"));
6459                 return;
6460             }
6461         }
6462         break;
6463
6464       case IcsPlayingBlack:
6465         /* User is moving for Black */
6466         if (WhiteOnMove(currentMove)) {
6467             if (!appData.premove) {
6468                 DisplayMoveError(_("It is White's turn"));
6469             } else if (toX >= 0 && toY >= 0) {
6470                 premoveToX = toX;
6471                 premoveToY = toY;
6472                 premoveFromX = fromX;
6473                 premoveFromY = fromY;
6474                 premovePromoChar = promoChar;
6475                 gotPremove = 1;
6476                 if (appData.debugMode)
6477                     fprintf(debugFP, "Got premove: fromX %d,"
6478                             "fromY %d, toX %d, toY %d\n",
6479                             fromX, fromY, toX, toY);
6480             }
6481             return;
6482         }
6483         break;
6484
6485       case IcsPlayingWhite:
6486         /* User is moving for White */
6487         if (!WhiteOnMove(currentMove)) {
6488             if (!appData.premove) {
6489                 DisplayMoveError(_("It is Black's turn"));
6490             } else if (toX >= 0 && toY >= 0) {
6491                 premoveToX = toX;
6492                 premoveToY = toY;
6493                 premoveFromX = fromX;
6494                 premoveFromY = fromY;
6495                 premovePromoChar = promoChar;
6496                 gotPremove = 1;
6497                 if (appData.debugMode)
6498                     fprintf(debugFP, "Got premove: fromX %d,"
6499                             "fromY %d, toX %d, toY %d\n",
6500                             fromX, fromY, toX, toY);
6501             }
6502             return;
6503         }
6504         break;
6505
6506       default:
6507         break;
6508
6509       case EditPosition:
6510         /* EditPosition, empty square, or different color piece;
6511            click-click move is possible */
6512         if (toX == -2 || toY == -2) {
6513             boards[0][fromY][fromX] = EmptySquare;
6514             DrawPosition(FALSE, boards[currentMove]);
6515             return;
6516         } else if (toX >= 0 && toY >= 0) {
6517             boards[0][toY][toX] = boards[0][fromY][fromX];
6518             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6519                 if(boards[0][fromY][0] != EmptySquare) {
6520                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6521                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6522                 }
6523             } else
6524             if(fromX == BOARD_RGHT+1) {
6525                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6526                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6527                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6528                 }
6529             } else
6530             boards[0][fromY][fromX] = EmptySquare;
6531             DrawPosition(FALSE, boards[currentMove]);
6532             return;
6533         }
6534         return;
6535     }
6536
6537     if(toX < 0 || toY < 0) return;
6538     pdown = boards[currentMove][fromY][fromX];
6539     pup = boards[currentMove][toY][toX];
6540
6541     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6542     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6543          if( pup != EmptySquare ) return;
6544          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6545            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6546                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6547            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6548            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6549            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6550            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6551          fromY = DROP_RANK;
6552     }
6553
6554     /* [HGM] always test for legality, to get promotion info */
6555     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6556                                          fromY, fromX, toY, toX, promoChar);
6557
6558     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6559
6560     /* [HGM] but possibly ignore an IllegalMove result */
6561     if (appData.testLegality) {
6562         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6563             DisplayMoveError(_("Illegal move"));
6564             return;
6565         }
6566     }
6567
6568     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6569 }
6570
6571 /* Common tail of UserMoveEvent and DropMenuEvent */
6572 int
6573 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6574      ChessMove moveType;
6575      int fromX, fromY, toX, toY;
6576      /*char*/int promoChar;
6577 {
6578     char *bookHit = 0;
6579
6580     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6581         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6582         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6583         if(WhiteOnMove(currentMove)) {
6584             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6585         } else {
6586             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6587         }
6588     }
6589
6590     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6591        move type in caller when we know the move is a legal promotion */
6592     if(moveType == NormalMove && promoChar)
6593         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6594
6595     /* [HGM] <popupFix> The following if has been moved here from
6596        UserMoveEvent(). Because it seemed to belong here (why not allow
6597        piece drops in training games?), and because it can only be
6598        performed after it is known to what we promote. */
6599     if (gameMode == Training) {
6600       /* compare the move played on the board to the next move in the
6601        * game. If they match, display the move and the opponent's response.
6602        * If they don't match, display an error message.
6603        */
6604       int saveAnimate;
6605       Board testBoard;
6606       CopyBoard(testBoard, boards[currentMove]);
6607       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6608
6609       if (CompareBoards(testBoard, boards[currentMove+1])) {
6610         ForwardInner(currentMove+1);
6611
6612         /* Autoplay the opponent's response.
6613          * if appData.animate was TRUE when Training mode was entered,
6614          * the response will be animated.
6615          */
6616         saveAnimate = appData.animate;
6617         appData.animate = animateTraining;
6618         ForwardInner(currentMove+1);
6619         appData.animate = saveAnimate;
6620
6621         /* check for the end of the game */
6622         if (currentMove >= forwardMostMove) {
6623           gameMode = PlayFromGameFile;
6624           ModeHighlight();
6625           SetTrainingModeOff();
6626           DisplayInformation(_("End of game"));
6627         }
6628       } else {
6629         DisplayError(_("Incorrect move"), 0);
6630       }
6631       return 1;
6632     }
6633
6634   /* Ok, now we know that the move is good, so we can kill
6635      the previous line in Analysis Mode */
6636   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6637                                 && currentMove < forwardMostMove) {
6638     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6639     else forwardMostMove = currentMove;
6640   }
6641
6642   /* If we need the chess program but it's dead, restart it */
6643   ResurrectChessProgram();
6644
6645   /* A user move restarts a paused game*/
6646   if (pausing)
6647     PauseEvent();
6648
6649   thinkOutput[0] = NULLCHAR;
6650
6651   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6652
6653   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6654     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655     return 1;
6656   }
6657
6658   if (gameMode == BeginningOfGame) {
6659     if (appData.noChessProgram) {
6660       gameMode = EditGame;
6661       SetGameInfo();
6662     } else {
6663       char buf[MSG_SIZ];
6664       gameMode = MachinePlaysBlack;
6665       StartClocks();
6666       SetGameInfo();
6667       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6668       DisplayTitle(buf);
6669       if (first.sendName) {
6670         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6671         SendToProgram(buf, &first);
6672       }
6673       StartClocks();
6674     }
6675     ModeHighlight();
6676   }
6677
6678   /* Relay move to ICS or chess engine */
6679   if (appData.icsActive) {
6680     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6681         gameMode == IcsExamining) {
6682       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6683         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6684         SendToICS("draw ");
6685         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6686       }
6687       // also send plain move, in case ICS does not understand atomic claims
6688       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6689       ics_user_moved = 1;
6690     }
6691   } else {
6692     if (first.sendTime && (gameMode == BeginningOfGame ||
6693                            gameMode == MachinePlaysWhite ||
6694                            gameMode == MachinePlaysBlack)) {
6695       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6696     }
6697     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6698          // [HGM] book: if program might be playing, let it use book
6699         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6700         first.maybeThinking = TRUE;
6701     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6702         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6703         SendBoard(&first, currentMove+1);
6704     } else SendMoveToProgram(forwardMostMove-1, &first);
6705     if (currentMove == cmailOldMove + 1) {
6706       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6707     }
6708   }
6709
6710   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6711
6712   switch (gameMode) {
6713   case EditGame:
6714     if(appData.testLegality)
6715     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6716     case MT_NONE:
6717     case MT_CHECK:
6718       break;
6719     case MT_CHECKMATE:
6720     case MT_STAINMATE:
6721       if (WhiteOnMove(currentMove)) {
6722         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6723       } else {
6724         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6725       }
6726       break;
6727     case MT_STALEMATE:
6728       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6729       break;
6730     }
6731     break;
6732
6733   case MachinePlaysBlack:
6734   case MachinePlaysWhite:
6735     /* disable certain menu options while machine is thinking */
6736     SetMachineThinkingEnables();
6737     break;
6738
6739   default:
6740     break;
6741   }
6742
6743   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6744   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6745
6746   if(bookHit) { // [HGM] book: simulate book reply
6747         static char bookMove[MSG_SIZ]; // a bit generous?
6748
6749         programStats.nodes = programStats.depth = programStats.time =
6750         programStats.score = programStats.got_only_move = 0;
6751         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6752
6753         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6754         strcat(bookMove, bookHit);
6755         HandleMachineMove(bookMove, &first);
6756   }
6757   return 1;
6758 }
6759
6760 void
6761 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6762      Board board;
6763      int flags;
6764      ChessMove kind;
6765      int rf, ff, rt, ft;
6766      VOIDSTAR closure;
6767 {
6768     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6769     Markers *m = (Markers *) closure;
6770     if(rf == fromY && ff == fromX)
6771         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6772                          || kind == WhiteCapturesEnPassant
6773                          || kind == BlackCapturesEnPassant);
6774     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6775 }
6776
6777 void
6778 MarkTargetSquares(int clear)
6779 {
6780   int x, y;
6781   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6782      !appData.testLegality || gameMode == EditPosition) return;
6783   if(clear) {
6784     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6785   } else {
6786     int capt = 0;
6787     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6788     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6789       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6790       if(capt)
6791       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6792     }
6793   }
6794   DrawPosition(TRUE, NULL);
6795 }
6796
6797 int
6798 Explode(Board board, int fromX, int fromY, int toX, int toY)
6799 {
6800     if(gameInfo.variant == VariantAtomic &&
6801        (board[toY][toX] != EmptySquare ||                     // capture?
6802         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6803                          board[fromY][fromX] == BlackPawn   )
6804       )) {
6805         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6806         return TRUE;
6807     }
6808     return FALSE;
6809 }
6810
6811 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6812
6813 int CanPromote(ChessSquare piece, int y)
6814 {
6815         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6816         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6817         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6818            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6819            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6820                                                   gameInfo.variant == VariantMakruk) return FALSE;
6821         return (piece == BlackPawn && y == 1 ||
6822                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6823                 piece == BlackLance && y == 1 ||
6824                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6825 }
6826
6827 void LeftClick(ClickType clickType, int xPix, int yPix)
6828 {
6829     int x, y;
6830     Boolean saveAnimate;
6831     static int second = 0, promotionChoice = 0, clearFlag = 0;
6832     char promoChoice = NULLCHAR;
6833     ChessSquare piece;
6834
6835     if(appData.seekGraph && appData.icsActive && loggedOn &&
6836         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6837         SeekGraphClick(clickType, xPix, yPix, 0);
6838         return;
6839     }
6840
6841     if (clickType == Press) ErrorPopDown();
6842
6843     x = EventToSquare(xPix, BOARD_WIDTH);
6844     y = EventToSquare(yPix, BOARD_HEIGHT);
6845     if (!flipView && y >= 0) {
6846         y = BOARD_HEIGHT - 1 - y;
6847     }
6848     if (flipView && x >= 0) {
6849         x = BOARD_WIDTH - 1 - x;
6850     }
6851
6852     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6853         defaultPromoChoice = promoSweep;
6854         promoSweep = EmptySquare;   // terminate sweep
6855         promoDefaultAltered = TRUE;
6856         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6857     }
6858
6859     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6860         if(clickType == Release) return; // ignore upclick of click-click destination
6861         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6862         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6863         if(gameInfo.holdingsWidth &&
6864                 (WhiteOnMove(currentMove)
6865                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6866                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6867             // click in right holdings, for determining promotion piece
6868             ChessSquare p = boards[currentMove][y][x];
6869             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6870             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6871             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6872                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6873                 fromX = fromY = -1;
6874                 return;
6875             }
6876         }
6877         DrawPosition(FALSE, boards[currentMove]);
6878         return;
6879     }
6880
6881     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6882     if(clickType == Press
6883             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6884               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6885               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6886         return;
6887
6888     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6889         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6890
6891     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6892         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6893                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6894         defaultPromoChoice = DefaultPromoChoice(side);
6895     }
6896
6897     autoQueen = appData.alwaysPromoteToQueen;
6898
6899     if (fromX == -1) {
6900       int originalY = y;
6901       gatingPiece = EmptySquare;
6902       if (clickType != Press) {
6903         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6904             DragPieceEnd(xPix, yPix); dragging = 0;
6905             DrawPosition(FALSE, NULL);
6906         }
6907         return;
6908       }
6909       fromX = x; fromY = y; toX = toY = -1;
6910       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6911          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6912          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6913             /* First square */
6914             if (OKToStartUserMove(fromX, fromY)) {
6915                 second = 0;
6916                 MarkTargetSquares(0);
6917                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6918                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6919                     promoSweep = defaultPromoChoice;
6920                     selectFlag = 0; lastX = xPix; lastY = yPix;
6921                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6922                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6923                 }
6924                 if (appData.highlightDragging) {
6925                     SetHighlights(fromX, fromY, -1, -1);
6926                 }
6927             } else fromX = fromY = -1;
6928             return;
6929         }
6930     }
6931
6932     /* fromX != -1 */
6933     if (clickType == Press && gameMode != EditPosition) {
6934         ChessSquare fromP;
6935         ChessSquare toP;
6936         int frc;
6937
6938         // ignore off-board to clicks
6939         if(y < 0 || x < 0) return;
6940
6941         /* Check if clicking again on the same color piece */
6942         fromP = boards[currentMove][fromY][fromX];
6943         toP = boards[currentMove][y][x];
6944         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6945         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6946              WhitePawn <= toP && toP <= WhiteKing &&
6947              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6948              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6949             (BlackPawn <= fromP && fromP <= BlackKing &&
6950              BlackPawn <= toP && toP <= BlackKing &&
6951              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6952              !(fromP == BlackKing && toP == BlackRook && frc))) {
6953             /* Clicked again on same color piece -- changed his mind */
6954             second = (x == fromX && y == fromY);
6955             promoDefaultAltered = FALSE;
6956             MarkTargetSquares(1);
6957            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6958             if (appData.highlightDragging) {
6959                 SetHighlights(x, y, -1, -1);
6960             } else {
6961                 ClearHighlights();
6962             }
6963             if (OKToStartUserMove(x, y)) {
6964                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6965                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6966                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6967                  gatingPiece = boards[currentMove][fromY][fromX];
6968                 else gatingPiece = EmptySquare;
6969                 fromX = x;
6970                 fromY = y; dragging = 1;
6971                 MarkTargetSquares(0);
6972                 DragPieceBegin(xPix, yPix, FALSE);
6973                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6974                     promoSweep = defaultPromoChoice;
6975                     selectFlag = 0; lastX = xPix; lastY = yPix;
6976                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6977                 }
6978             }
6979            }
6980            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6981            second = FALSE; 
6982         }
6983         // ignore clicks on holdings
6984         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6985     }
6986
6987     if (clickType == Release && x == fromX && y == fromY) {
6988         DragPieceEnd(xPix, yPix); dragging = 0;
6989         if(clearFlag) {
6990             // a deferred attempt to click-click move an empty square on top of a piece
6991             boards[currentMove][y][x] = EmptySquare;
6992             ClearHighlights();
6993             DrawPosition(FALSE, boards[currentMove]);
6994             fromX = fromY = -1; clearFlag = 0;
6995             return;
6996         }
6997         if (appData.animateDragging) {
6998             /* Undo animation damage if any */
6999             DrawPosition(FALSE, NULL);
7000         }
7001         if (second) {
7002             /* Second up/down in same square; just abort move */
7003             second = 0;
7004             fromX = fromY = -1;
7005             gatingPiece = EmptySquare;
7006             ClearHighlights();
7007             gotPremove = 0;
7008             ClearPremoveHighlights();
7009         } else {
7010             /* First upclick in same square; start click-click mode */
7011             SetHighlights(x, y, -1, -1);
7012         }
7013         return;
7014     }
7015
7016     clearFlag = 0;
7017
7018     /* we now have a different from- and (possibly off-board) to-square */
7019     /* Completed move */
7020     toX = x;
7021     toY = y;
7022     saveAnimate = appData.animate;
7023     MarkTargetSquares(1);
7024     if (clickType == Press) {
7025         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7026             // must be Edit Position mode with empty-square selected
7027             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7028             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7029             return;
7030         }
7031         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7032             ChessSquare piece = boards[currentMove][fromY][fromX];
7033             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7034             promoSweep = defaultPromoChoice;
7035             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7036             selectFlag = 0; lastX = xPix; lastY = yPix;
7037             Sweep(0); // Pawn that is going to promote: preview promotion piece
7038             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7039             DrawPosition(FALSE, boards[currentMove]);
7040             return;
7041         }
7042         /* Finish clickclick move */
7043         if (appData.animate || appData.highlightLastMove) {
7044             SetHighlights(fromX, fromY, toX, toY);
7045         } else {
7046             ClearHighlights();
7047         }
7048     } else {
7049         /* Finish drag move */
7050         if (appData.highlightLastMove) {
7051             SetHighlights(fromX, fromY, toX, toY);
7052         } else {
7053             ClearHighlights();
7054         }
7055         DragPieceEnd(xPix, yPix); dragging = 0;
7056         /* Don't animate move and drag both */
7057         appData.animate = FALSE;
7058     }
7059
7060     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7061     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7062         ChessSquare piece = boards[currentMove][fromY][fromX];
7063         if(gameMode == EditPosition && piece != EmptySquare &&
7064            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7065             int n;
7066
7067             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7068                 n = PieceToNumber(piece - (int)BlackPawn);
7069                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7070                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7071                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7072             } else
7073             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7074                 n = PieceToNumber(piece);
7075                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7076                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7077                 boards[currentMove][n][BOARD_WIDTH-2]++;
7078             }
7079             boards[currentMove][fromY][fromX] = EmptySquare;
7080         }
7081         ClearHighlights();
7082         fromX = fromY = -1;
7083         DrawPosition(TRUE, boards[currentMove]);
7084         return;
7085     }
7086
7087     // off-board moves should not be highlighted
7088     if(x < 0 || y < 0) ClearHighlights();
7089
7090     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7091
7092     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7093         SetHighlights(fromX, fromY, toX, toY);
7094         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7095             // [HGM] super: promotion to captured piece selected from holdings
7096             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7097             promotionChoice = TRUE;
7098             // kludge follows to temporarily execute move on display, without promoting yet
7099             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7100             boards[currentMove][toY][toX] = p;
7101             DrawPosition(FALSE, boards[currentMove]);
7102             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7103             boards[currentMove][toY][toX] = q;
7104             DisplayMessage("Click in holdings to choose piece", "");
7105             return;
7106         }
7107         PromotionPopUp();
7108     } else {
7109         int oldMove = currentMove;
7110         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7111         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7112         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7113         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7114            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7115             DrawPosition(TRUE, boards[currentMove]);
7116         fromX = fromY = -1;
7117     }
7118     appData.animate = saveAnimate;
7119     if (appData.animate || appData.animateDragging) {
7120         /* Undo animation damage if needed */
7121         DrawPosition(FALSE, NULL);
7122     }
7123 }
7124
7125 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7126 {   // front-end-free part taken out of PieceMenuPopup
7127     int whichMenu; int xSqr, ySqr;
7128
7129     if(seekGraphUp) { // [HGM] seekgraph
7130         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7131         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7132         return -2;
7133     }
7134
7135     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7136          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7137         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7138         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7139         if(action == Press)   {
7140             originalFlip = flipView;
7141             flipView = !flipView; // temporarily flip board to see game from partners perspective
7142             DrawPosition(TRUE, partnerBoard);
7143             DisplayMessage(partnerStatus, "");
7144             partnerUp = TRUE;
7145         } else if(action == Release) {
7146             flipView = originalFlip;
7147             DrawPosition(TRUE, boards[currentMove]);
7148             partnerUp = FALSE;
7149         }
7150         return -2;
7151     }
7152
7153     xSqr = EventToSquare(x, BOARD_WIDTH);
7154     ySqr = EventToSquare(y, BOARD_HEIGHT);
7155     if (action == Release) {
7156         if(pieceSweep != EmptySquare) {
7157             EditPositionMenuEvent(pieceSweep, toX, toY);
7158             pieceSweep = EmptySquare;
7159         } else UnLoadPV(); // [HGM] pv
7160     }
7161     if (action != Press) return -2; // return code to be ignored
7162     switch (gameMode) {
7163       case IcsExamining:
7164         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7165       case EditPosition:
7166         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7167         if (xSqr < 0 || ySqr < 0) return -1;
7168         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7169         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7170         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7171         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7172         NextPiece(0);
7173         return 2; // grab
7174       case IcsObserving:
7175         if(!appData.icsEngineAnalyze) return -1;
7176       case IcsPlayingWhite:
7177       case IcsPlayingBlack:
7178         if(!appData.zippyPlay) goto noZip;
7179       case AnalyzeMode:
7180       case AnalyzeFile:
7181       case MachinePlaysWhite:
7182       case MachinePlaysBlack:
7183       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7184         if (!appData.dropMenu) {
7185           LoadPV(x, y);
7186           return 2; // flag front-end to grab mouse events
7187         }
7188         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7189            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7190       case EditGame:
7191       noZip:
7192         if (xSqr < 0 || ySqr < 0) return -1;
7193         if (!appData.dropMenu || appData.testLegality &&
7194             gameInfo.variant != VariantBughouse &&
7195             gameInfo.variant != VariantCrazyhouse) return -1;
7196         whichMenu = 1; // drop menu
7197         break;
7198       default:
7199         return -1;
7200     }
7201
7202     if (((*fromX = xSqr) < 0) ||
7203         ((*fromY = ySqr) < 0)) {
7204         *fromX = *fromY = -1;
7205         return -1;
7206     }
7207     if (flipView)
7208       *fromX = BOARD_WIDTH - 1 - *fromX;
7209     else
7210       *fromY = BOARD_HEIGHT - 1 - *fromY;
7211
7212     return whichMenu;
7213 }
7214
7215 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7216 {
7217 //    char * hint = lastHint;
7218     FrontEndProgramStats stats;
7219
7220     stats.which = cps == &first ? 0 : 1;
7221     stats.depth = cpstats->depth;
7222     stats.nodes = cpstats->nodes;
7223     stats.score = cpstats->score;
7224     stats.time = cpstats->time;
7225     stats.pv = cpstats->movelist;
7226     stats.hint = lastHint;
7227     stats.an_move_index = 0;
7228     stats.an_move_count = 0;
7229
7230     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7231         stats.hint = cpstats->move_name;
7232         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7233         stats.an_move_count = cpstats->nr_moves;
7234     }
7235
7236     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
7237
7238     SetProgramStats( &stats );
7239 }
7240
7241 void
7242 ClearEngineOutputPane(int which)
7243 {
7244     static FrontEndProgramStats dummyStats;
7245     dummyStats.which = which;
7246     dummyStats.pv = "#";
7247     SetProgramStats( &dummyStats );
7248 }
7249
7250 #define MAXPLAYERS 500
7251
7252 char *
7253 TourneyStandings(int display)
7254 {
7255     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7256     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7257     char result, *p, *names[MAXPLAYERS];
7258
7259     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7260         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7261     names[0] = p = strdup(appData.participants);
7262     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7263
7264     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7265
7266     while(result = appData.results[nr]) {
7267         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7268         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7269         wScore = bScore = 0;
7270         switch(result) {
7271           case '+': wScore = 2; break;
7272           case '-': bScore = 2; break;
7273           case '=': wScore = bScore = 1; break;
7274           case ' ':
7275           case '*': return strdup("busy"); // tourney not finished
7276         }
7277         score[w] += wScore;
7278         score[b] += bScore;
7279         games[w]++;
7280         games[b]++;
7281         nr++;
7282     }
7283     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7284     for(w=0; w<nPlayers; w++) {
7285         bScore = -1;
7286         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7287         ranking[w] = b; points[w] = bScore; score[b] = -2;
7288     }
7289     p = malloc(nPlayers*34+1);
7290     for(w=0; w<nPlayers && w<display; w++)
7291         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7292     free(names[0]);
7293     return p;
7294 }
7295
7296 void
7297 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7298 {       // count all piece types
7299         int p, f, r;
7300         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7301         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7302         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7303                 p = board[r][f];
7304                 pCnt[p]++;
7305                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7306                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7307                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7308                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7309                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7310                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7311         }
7312 }
7313
7314 int
7315 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7316 {
7317         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7318         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7319
7320         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7321         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7322         if(myPawns == 2 && nMine == 3) // KPP
7323             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7324         if(myPawns == 1 && nMine == 2) // KP
7325             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7326         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7327             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7328         if(myPawns) return FALSE;
7329         if(pCnt[WhiteRook+side])
7330             return pCnt[BlackRook-side] ||
7331                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7332                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7333                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7334         if(pCnt[WhiteCannon+side]) {
7335             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7336             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7337         }
7338         if(pCnt[WhiteKnight+side])
7339             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7340         return FALSE;
7341 }
7342
7343 int
7344 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7345 {
7346         VariantClass v = gameInfo.variant;
7347
7348         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7349         if(v == VariantShatranj) return TRUE; // always winnable through baring
7350         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7351         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7352
7353         if(v == VariantXiangqi) {
7354                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7355
7356                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7357                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7358                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7359                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7360                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7361                 if(stale) // we have at least one last-rank P plus perhaps C
7362                     return majors // KPKX
7363                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7364                 else // KCA*E*
7365                     return pCnt[WhiteFerz+side] // KCAK
7366                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7367                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7368                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7369
7370         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7371                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7372
7373                 if(nMine == 1) return FALSE; // bare King
7374                 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
7375                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7376                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7377                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7378                 if(pCnt[WhiteKnight+side])
7379                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7380                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7381                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7382                 if(nBishops)
7383                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7384                 if(pCnt[WhiteAlfil+side])
7385                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7386                 if(pCnt[WhiteWazir+side])
7387                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7388         }
7389
7390         return TRUE;
7391 }
7392
7393 int
7394 CompareWithRights(Board b1, Board b2)
7395 {
7396     int rights = 0;
7397     if(!CompareBoards(b1, b2)) return FALSE;
7398     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7399     /* compare castling rights */
7400     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7401            rights++; /* King lost rights, while rook still had them */
7402     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7403         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7404            rights++; /* but at least one rook lost them */
7405     }
7406     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7407            rights++;
7408     if( b1[CASTLING][5] != NoRights ) {
7409         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7410            rights++;
7411     }
7412     return rights == 0;
7413 }
7414
7415 int
7416 Adjudicate(ChessProgramState *cps)
7417 {       // [HGM] some adjudications useful with buggy engines
7418         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7419         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7420         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7421         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7422         int k, count = 0; static int bare = 1;
7423         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7424         Boolean canAdjudicate = !appData.icsActive;
7425
7426         // most tests only when we understand the game, i.e. legality-checking on
7427             if( appData.testLegality )
7428             {   /* [HGM] Some more adjudications for obstinate engines */
7429                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7430                 static int moveCount = 6;
7431                 ChessMove result;
7432                 char *reason = NULL;
7433
7434                 /* Count what is on board. */
7435                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7436
7437                 /* Some material-based adjudications that have to be made before stalemate test */
7438                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7439                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7440                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7441                      if(canAdjudicate && appData.checkMates) {
7442                          if(engineOpponent)
7443                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7444                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7445                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7446                          return 1;
7447                      }
7448                 }
7449
7450                 /* Bare King in Shatranj (loses) or Losers (wins) */
7451                 if( nrW == 1 || nrB == 1) {
7452                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7453                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7454                      if(canAdjudicate && appData.checkMates) {
7455                          if(engineOpponent)
7456                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7457                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7458                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7459                          return 1;
7460                      }
7461                   } else
7462                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7463                   {    /* bare King */
7464                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7465                         if(canAdjudicate && appData.checkMates) {
7466                             /* but only adjudicate if adjudication enabled */
7467                             if(engineOpponent)
7468                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7469                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7470                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7471                             return 1;
7472                         }
7473                   }
7474                 } else bare = 1;
7475
7476
7477             // don't wait for engine to announce game end if we can judge ourselves
7478             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7479               case MT_CHECK:
7480                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7481                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7482                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7483                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7484                             checkCnt++;
7485                         if(checkCnt >= 2) {
7486                             reason = "Xboard adjudication: 3rd check";
7487                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7488                             break;
7489                         }
7490                     }
7491                 }
7492               case MT_NONE:
7493               default:
7494                 break;
7495               case MT_STALEMATE:
7496               case MT_STAINMATE:
7497                 reason = "Xboard adjudication: Stalemate";
7498                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7499                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7500                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7501                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7502                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7503                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7504                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7505                                                                         EP_CHECKMATE : EP_WINS);
7506                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7507                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7508                 }
7509                 break;
7510               case MT_CHECKMATE:
7511                 reason = "Xboard adjudication: Checkmate";
7512                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7513                 break;
7514             }
7515
7516                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7517                     case EP_STALEMATE:
7518                         result = GameIsDrawn; break;
7519                     case EP_CHECKMATE:
7520                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7521                     case EP_WINS:
7522                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7523                     default:
7524                         result = EndOfFile;
7525                 }
7526                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7527                     if(engineOpponent)
7528                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7529                     GameEnds( result, reason, GE_XBOARD );
7530                     return 1;
7531                 }
7532
7533                 /* Next absolutely insufficient mating material. */
7534                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7535                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7536                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7537
7538                      /* always flag draws, for judging claims */
7539                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7540
7541                      if(canAdjudicate && appData.materialDraws) {
7542                          /* but only adjudicate them if adjudication enabled */
7543                          if(engineOpponent) {
7544                            SendToProgram("force\n", engineOpponent); // suppress reply
7545                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7546                          }
7547                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7548                          return 1;
7549                      }
7550                 }
7551
7552                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7553                 if(gameInfo.variant == VariantXiangqi ?
7554                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7555                  : nrW + nrB == 4 &&
7556                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7557                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7558                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7559                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7560                    ) ) {
7561                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7562                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7563                           if(engineOpponent) {
7564                             SendToProgram("force\n", engineOpponent); // suppress reply
7565                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7566                           }
7567                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7568                           return 1;
7569                      }
7570                 } else moveCount = 6;
7571             }
7572         if (appData.debugMode) { int i;
7573             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7574                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7575                     appData.drawRepeats);
7576             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7577               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7578
7579         }
7580
7581         // Repetition draws and 50-move rule can be applied independently of legality testing
7582
7583                 /* Check for rep-draws */
7584                 count = 0;
7585                 for(k = forwardMostMove-2;
7586                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7587                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7588                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7589                     k-=2)
7590                 {   int rights=0;
7591                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7592                         /* compare castling rights */
7593                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7594                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7595                                 rights++; /* King lost rights, while rook still had them */
7596                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7597                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7598                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7599                                    rights++; /* but at least one rook lost them */
7600                         }
7601                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7602                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7603                                 rights++;
7604                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7605                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7606                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7607                                    rights++;
7608                         }
7609                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7610                             && appData.drawRepeats > 1) {
7611                              /* adjudicate after user-specified nr of repeats */
7612                              int result = GameIsDrawn;
7613                              char *details = "XBoard adjudication: repetition draw";
7614                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7615                                 // [HGM] xiangqi: check for forbidden perpetuals
7616                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7617                                 for(m=forwardMostMove; m>k; m-=2) {
7618                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7619                                         ourPerpetual = 0; // the current mover did not always check
7620                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7621                                         hisPerpetual = 0; // the opponent did not always check
7622                                 }
7623                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7624                                                                         ourPerpetual, hisPerpetual);
7625                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7626                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7627                                     details = "Xboard adjudication: perpetual checking";
7628                                 } else
7629                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7630                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7631                                 } else
7632                                 // Now check for perpetual chases
7633                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7634                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7635                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7636                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7637                                         static char resdet[MSG_SIZ];
7638                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7639                                         details = resdet;
7640                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7641                                     } else
7642                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7643                                         break; // Abort repetition-checking loop.
7644                                 }
7645                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7646                              }
7647                              if(engineOpponent) {
7648                                SendToProgram("force\n", engineOpponent); // suppress reply
7649                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7650                              }
7651                              GameEnds( result, details, GE_XBOARD );
7652                              return 1;
7653                         }
7654                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7655                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7656                     }
7657                 }
7658
7659                 /* Now we test for 50-move draws. Determine ply count */
7660                 count = forwardMostMove;
7661                 /* look for last irreversble move */
7662                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7663                     count--;
7664                 /* if we hit starting position, add initial plies */
7665                 if( count == backwardMostMove )
7666                     count -= initialRulePlies;
7667                 count = forwardMostMove - count;
7668                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7669                         // adjust reversible move counter for checks in Xiangqi
7670                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7671                         if(i < backwardMostMove) i = backwardMostMove;
7672                         while(i <= forwardMostMove) {
7673                                 lastCheck = inCheck; // check evasion does not count
7674                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7675                                 if(inCheck || lastCheck) count--; // check does not count
7676                                 i++;
7677                         }
7678                 }
7679                 if( count >= 100)
7680                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7681                          /* this is used to judge if draw claims are legal */
7682                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7683                          if(engineOpponent) {
7684                            SendToProgram("force\n", engineOpponent); // suppress reply
7685                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7686                          }
7687                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7688                          return 1;
7689                 }
7690
7691                 /* if draw offer is pending, treat it as a draw claim
7692                  * when draw condition present, to allow engines a way to
7693                  * claim draws before making their move to avoid a race
7694                  * condition occurring after their move
7695                  */
7696                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7697                          char *p = NULL;
7698                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7699                              p = "Draw claim: 50-move rule";
7700                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7701                              p = "Draw claim: 3-fold repetition";
7702                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7703                              p = "Draw claim: insufficient mating material";
7704                          if( p != NULL && canAdjudicate) {
7705                              if(engineOpponent) {
7706                                SendToProgram("force\n", engineOpponent); // suppress reply
7707                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7708                              }
7709                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7710                              return 1;
7711                          }
7712                 }
7713
7714                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7715                     if(engineOpponent) {
7716                       SendToProgram("force\n", engineOpponent); // suppress reply
7717                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                     }
7719                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7720                     return 1;
7721                 }
7722         return 0;
7723 }
7724
7725 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7726 {   // [HGM] book: this routine intercepts moves to simulate book replies
7727     char *bookHit = NULL;
7728
7729     //first determine if the incoming move brings opponent into his book
7730     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7731         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7732     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7733     if(bookHit != NULL && !cps->bookSuspend) {
7734         // make sure opponent is not going to reply after receiving move to book position
7735         SendToProgram("force\n", cps);
7736         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7737     }
7738     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7739     // now arrange restart after book miss
7740     if(bookHit) {
7741         // after a book hit we never send 'go', and the code after the call to this routine
7742         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7743         char buf[MSG_SIZ], *move = bookHit;
7744         if(cps->useSAN) {
7745             int fromX, fromY, toX, toY;
7746             char promoChar;
7747             ChessMove moveType;
7748             move = buf + 30;
7749             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7750                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7751                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7752                                     PosFlags(forwardMostMove),
7753                                     fromY, fromX, toY, toX, promoChar, move);
7754             } else {
7755                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7756                 bookHit = NULL;
7757             }
7758         }
7759         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7760         SendToProgram(buf, cps);
7761         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7762     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7763         SendToProgram("go\n", cps);
7764         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7765     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7766         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7767             SendToProgram("go\n", cps);
7768         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7769     }
7770     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7771 }
7772
7773 char *savedMessage;
7774 ChessProgramState *savedState;
7775 void DeferredBookMove(void)
7776 {
7777         if(savedState->lastPing != savedState->lastPong)
7778                     ScheduleDelayedEvent(DeferredBookMove, 10);
7779         else
7780         HandleMachineMove(savedMessage, savedState);
7781 }
7782
7783 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7784
7785 void
7786 HandleMachineMove(message, cps)
7787      char *message;
7788      ChessProgramState *cps;
7789 {
7790     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7791     char realname[MSG_SIZ];
7792     int fromX, fromY, toX, toY;
7793     ChessMove moveType;
7794     char promoChar;
7795     char *p, *pv=buf1;
7796     int machineWhite;
7797     char *bookHit;
7798
7799     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7800         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7801         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7802             DisplayError(_("Invalid pairing from pairing engine"), 0);
7803             return;
7804         }
7805         pairingReceived = 1;
7806         NextMatchGame();
7807         return; // Skim the pairing messages here.
7808     }
7809
7810     cps->userError = 0;
7811
7812 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7813     /*
7814      * Kludge to ignore BEL characters
7815      */
7816     while (*message == '\007') message++;
7817
7818     /*
7819      * [HGM] engine debug message: ignore lines starting with '#' character
7820      */
7821     if(cps->debug && *message == '#') return;
7822
7823     /*
7824      * Look for book output
7825      */
7826     if (cps == &first && bookRequested) {
7827         if (message[0] == '\t' || message[0] == ' ') {
7828             /* Part of the book output is here; append it */
7829             strcat(bookOutput, message);
7830             strcat(bookOutput, "  \n");
7831             return;
7832         } else if (bookOutput[0] != NULLCHAR) {
7833             /* All of book output has arrived; display it */
7834             char *p = bookOutput;
7835             while (*p != NULLCHAR) {
7836                 if (*p == '\t') *p = ' ';
7837                 p++;
7838             }
7839             DisplayInformation(bookOutput);
7840             bookRequested = FALSE;
7841             /* Fall through to parse the current output */
7842         }
7843     }
7844
7845     /*
7846      * Look for machine move.
7847      */
7848     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7849         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7850     {
7851         /* This method is only useful on engines that support ping */
7852         if (cps->lastPing != cps->lastPong) {
7853           if (gameMode == BeginningOfGame) {
7854             /* Extra move from before last new; ignore */
7855             if (appData.debugMode) {
7856                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7857             }
7858           } else {
7859             if (appData.debugMode) {
7860                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7861                         cps->which, gameMode);
7862             }
7863
7864             SendToProgram("undo\n", cps);
7865           }
7866           return;
7867         }
7868
7869         switch (gameMode) {
7870           case BeginningOfGame:
7871             /* Extra move from before last reset; ignore */
7872             if (appData.debugMode) {
7873                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7874             }
7875             return;
7876
7877           case EndOfGame:
7878           case IcsIdle:
7879           default:
7880             /* Extra move after we tried to stop.  The mode test is
7881                not a reliable way of detecting this problem, but it's
7882                the best we can do on engines that don't support ping.
7883             */
7884             if (appData.debugMode) {
7885                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7886                         cps->which, gameMode);
7887             }
7888             SendToProgram("undo\n", cps);
7889             return;
7890
7891           case MachinePlaysWhite:
7892           case IcsPlayingWhite:
7893             machineWhite = TRUE;
7894             break;
7895
7896           case MachinePlaysBlack:
7897           case IcsPlayingBlack:
7898             machineWhite = FALSE;
7899             break;
7900
7901           case TwoMachinesPlay:
7902             machineWhite = (cps->twoMachinesColor[0] == 'w');
7903             break;
7904         }
7905         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7906             if (appData.debugMode) {
7907                 fprintf(debugFP,
7908                         "Ignoring move out of turn by %s, gameMode %d"
7909                         ", forwardMost %d\n",
7910                         cps->which, gameMode, forwardMostMove);
7911             }
7912             return;
7913         }
7914
7915     if (appData.debugMode) { int f = forwardMostMove;
7916         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7917                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7918                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7919     }
7920         if(cps->alphaRank) AlphaRank(machineMove, 4);
7921         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7922                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7923             /* Machine move could not be parsed; ignore it. */
7924           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7925                     machineMove, _(cps->which));
7926             DisplayError(buf1, 0);
7927             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7928                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7929             if (gameMode == TwoMachinesPlay) {
7930               GameEnds(machineWhite ? BlackWins : WhiteWins,
7931                        buf1, GE_XBOARD);
7932             }
7933             return;
7934         }
7935
7936         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7937         /* So we have to redo legality test with true e.p. status here,  */
7938         /* to make sure an illegal e.p. capture does not slip through,   */
7939         /* to cause a forfeit on a justified illegal-move complaint      */
7940         /* of the opponent.                                              */
7941         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7942            ChessMove moveType;
7943            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7944                              fromY, fromX, toY, toX, promoChar);
7945             if (appData.debugMode) {
7946                 int i;
7947                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7948                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7949                 fprintf(debugFP, "castling rights\n");
7950             }
7951             if(moveType == IllegalMove) {
7952               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7953                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7954                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7955                            buf1, GE_XBOARD);
7956                 return;
7957            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7958            /* [HGM] Kludge to handle engines that send FRC-style castling
7959               when they shouldn't (like TSCP-Gothic) */
7960            switch(moveType) {
7961              case WhiteASideCastleFR:
7962              case BlackASideCastleFR:
7963                toX+=2;
7964                currentMoveString[2]++;
7965                break;
7966              case WhiteHSideCastleFR:
7967              case BlackHSideCastleFR:
7968                toX--;
7969                currentMoveString[2]--;
7970                break;
7971              default: ; // nothing to do, but suppresses warning of pedantic compilers
7972            }
7973         }
7974         hintRequested = FALSE;
7975         lastHint[0] = NULLCHAR;
7976         bookRequested = FALSE;
7977         /* Program may be pondering now */
7978         cps->maybeThinking = TRUE;
7979         if (cps->sendTime == 2) cps->sendTime = 1;
7980         if (cps->offeredDraw) cps->offeredDraw--;
7981
7982         /* [AS] Save move info*/
7983         pvInfoList[ forwardMostMove ].score = programStats.score;
7984         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7985         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7986
7987         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7988
7989         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7990         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7991             int count = 0;
7992
7993             while( count < adjudicateLossPlies ) {
7994                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7995
7996                 if( count & 1 ) {
7997                     score = -score; /* Flip score for winning side */
7998                 }
7999
8000                 if( score > adjudicateLossThreshold ) {
8001                     break;
8002                 }
8003
8004                 count++;
8005             }
8006
8007             if( count >= adjudicateLossPlies ) {
8008                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8009
8010                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8011                     "Xboard adjudication",
8012                     GE_XBOARD );
8013
8014                 return;
8015             }
8016         }
8017
8018         if(Adjudicate(cps)) {
8019             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8020             return; // [HGM] adjudicate: for all automatic game ends
8021         }
8022
8023 #if ZIPPY
8024         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8025             first.initDone) {
8026           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8027                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8028                 SendToICS("draw ");
8029                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8030           }
8031           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8032           ics_user_moved = 1;
8033           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8034                 char buf[3*MSG_SIZ];
8035
8036                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8037                         programStats.score / 100.,
8038                         programStats.depth,
8039                         programStats.time / 100.,
8040                         (unsigned int)programStats.nodes,
8041                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8042                         programStats.movelist);
8043                 SendToICS(buf);
8044 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8045           }
8046         }
8047 #endif
8048
8049         /* [AS] Clear stats for next move */
8050         ClearProgramStats();
8051         thinkOutput[0] = NULLCHAR;
8052         hiddenThinkOutputState = 0;
8053
8054         bookHit = NULL;
8055         if (gameMode == TwoMachinesPlay) {
8056             /* [HGM] relaying draw offers moved to after reception of move */
8057             /* and interpreting offer as claim if it brings draw condition */
8058             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8059                 SendToProgram("draw\n", cps->other);
8060             }
8061             if (cps->other->sendTime) {
8062                 SendTimeRemaining(cps->other,
8063                                   cps->other->twoMachinesColor[0] == 'w');
8064             }
8065             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8066             if (firstMove && !bookHit) {
8067                 firstMove = FALSE;
8068                 if (cps->other->useColors) {
8069                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8070                 }
8071                 SendToProgram("go\n", cps->other);
8072             }
8073             cps->other->maybeThinking = TRUE;
8074         }
8075
8076         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8077
8078         if (!pausing && appData.ringBellAfterMoves) {
8079             RingBell();
8080         }
8081
8082         /*
8083          * Reenable menu items that were disabled while
8084          * machine was thinking
8085          */
8086         if (gameMode != TwoMachinesPlay)
8087             SetUserThinkingEnables();
8088
8089         // [HGM] book: after book hit opponent has received move and is now in force mode
8090         // force the book reply into it, and then fake that it outputted this move by jumping
8091         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8092         if(bookHit) {
8093                 static char bookMove[MSG_SIZ]; // a bit generous?
8094
8095                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8096                 strcat(bookMove, bookHit);
8097                 message = bookMove;
8098                 cps = cps->other;
8099                 programStats.nodes = programStats.depth = programStats.time =
8100                 programStats.score = programStats.got_only_move = 0;
8101                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8102
8103                 if(cps->lastPing != cps->lastPong) {
8104                     savedMessage = message; // args for deferred call
8105                     savedState = cps;
8106                     ScheduleDelayedEvent(DeferredBookMove, 10);
8107                     return;
8108                 }
8109                 goto FakeBookMove;
8110         }
8111
8112         return;
8113     }
8114
8115     /* Set special modes for chess engines.  Later something general
8116      *  could be added here; for now there is just one kludge feature,
8117      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8118      *  when "xboard" is given as an interactive command.
8119      */
8120     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8121         cps->useSigint = FALSE;
8122         cps->useSigterm = FALSE;
8123     }
8124     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8125       ParseFeatures(message+8, cps);
8126       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8127     }
8128
8129     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8130                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8131       int dummy, s=6; char buf[MSG_SIZ];
8132       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8133       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8134       if(startedFromSetupPosition) return;
8135       ParseFEN(boards[0], &dummy, message+s);
8136       DrawPosition(TRUE, boards[0]);
8137       startedFromSetupPosition = TRUE;
8138       return;
8139     }
8140     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8141      * want this, I was asked to put it in, and obliged.
8142      */
8143     if (!strncmp(message, "setboard ", 9)) {
8144         Board initial_position;
8145
8146         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8147
8148         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8149             DisplayError(_("Bad FEN received from engine"), 0);
8150             return ;
8151         } else {
8152            Reset(TRUE, FALSE);
8153            CopyBoard(boards[0], initial_position);
8154            initialRulePlies = FENrulePlies;
8155            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8156            else gameMode = MachinePlaysBlack;
8157            DrawPosition(FALSE, boards[currentMove]);
8158         }
8159         return;
8160     }
8161
8162     /*
8163      * Look for communication commands
8164      */
8165     if (!strncmp(message, "telluser ", 9)) {
8166         if(message[9] == '\\' && message[10] == '\\')
8167             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8168         PlayTellSound();
8169         DisplayNote(message + 9);
8170         return;
8171     }
8172     if (!strncmp(message, "tellusererror ", 14)) {
8173         cps->userError = 1;
8174         if(message[14] == '\\' && message[15] == '\\')
8175             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8176         PlayTellSound();
8177         DisplayError(message + 14, 0);
8178         return;
8179     }
8180     if (!strncmp(message, "tellopponent ", 13)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8184           SendToICS(buf1);
8185         }
8186       } else {
8187         DisplayNote(message + 13);
8188       }
8189       return;
8190     }
8191     if (!strncmp(message, "tellothers ", 11)) {
8192       if (appData.icsActive) {
8193         if (loggedOn) {
8194           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8195           SendToICS(buf1);
8196         }
8197       }
8198       return;
8199     }
8200     if (!strncmp(message, "tellall ", 8)) {
8201       if (appData.icsActive) {
8202         if (loggedOn) {
8203           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8204           SendToICS(buf1);
8205         }
8206       } else {
8207         DisplayNote(message + 8);
8208       }
8209       return;
8210     }
8211     if (strncmp(message, "warning", 7) == 0) {
8212         /* Undocumented feature, use tellusererror in new code */
8213         DisplayError(message, 0);
8214         return;
8215     }
8216     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8217         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8218         strcat(realname, " query");
8219         AskQuestion(realname, buf2, buf1, cps->pr);
8220         return;
8221     }
8222     /* Commands from the engine directly to ICS.  We don't allow these to be
8223      *  sent until we are logged on. Crafty kibitzes have been known to
8224      *  interfere with the login process.
8225      */
8226     if (loggedOn) {
8227         if (!strncmp(message, "tellics ", 8)) {
8228             SendToICS(message + 8);
8229             SendToICS("\n");
8230             return;
8231         }
8232         if (!strncmp(message, "tellicsnoalias ", 15)) {
8233             SendToICS(ics_prefix);
8234             SendToICS(message + 15);
8235             SendToICS("\n");
8236             return;
8237         }
8238         /* The following are for backward compatibility only */
8239         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8240             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8241             SendToICS(ics_prefix);
8242             SendToICS(message);
8243             SendToICS("\n");
8244             return;
8245         }
8246     }
8247     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8248         return;
8249     }
8250     /*
8251      * If the move is illegal, cancel it and redraw the board.
8252      * Also deal with other error cases.  Matching is rather loose
8253      * here to accommodate engines written before the spec.
8254      */
8255     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8256         strncmp(message, "Error", 5) == 0) {
8257         if (StrStr(message, "name") ||
8258             StrStr(message, "rating") || StrStr(message, "?") ||
8259             StrStr(message, "result") || StrStr(message, "board") ||
8260             StrStr(message, "bk") || StrStr(message, "computer") ||
8261             StrStr(message, "variant") || StrStr(message, "hint") ||
8262             StrStr(message, "random") || StrStr(message, "depth") ||
8263             StrStr(message, "accepted")) {
8264             return;
8265         }
8266         if (StrStr(message, "protover")) {
8267           /* Program is responding to input, so it's apparently done
8268              initializing, and this error message indicates it is
8269              protocol version 1.  So we don't need to wait any longer
8270              for it to initialize and send feature commands. */
8271           FeatureDone(cps, 1);
8272           cps->protocolVersion = 1;
8273           return;
8274         }
8275         cps->maybeThinking = FALSE;
8276
8277         if (StrStr(message, "draw")) {
8278             /* Program doesn't have "draw" command */
8279             cps->sendDrawOffers = 0;
8280             return;
8281         }
8282         if (cps->sendTime != 1 &&
8283             (StrStr(message, "time") || StrStr(message, "otim"))) {
8284           /* Program apparently doesn't have "time" or "otim" command */
8285           cps->sendTime = 0;
8286           return;
8287         }
8288         if (StrStr(message, "analyze")) {
8289             cps->analysisSupport = FALSE;
8290             cps->analyzing = FALSE;
8291 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8292             EditGameEvent(); // [HGM] try to preserve loaded game
8293             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8294             DisplayError(buf2, 0);
8295             return;
8296         }
8297         if (StrStr(message, "(no matching move)st")) {
8298           /* Special kludge for GNU Chess 4 only */
8299           cps->stKludge = TRUE;
8300           SendTimeControl(cps, movesPerSession, timeControl,
8301                           timeIncrement, appData.searchDepth,
8302                           searchTime);
8303           return;
8304         }
8305         if (StrStr(message, "(no matching move)sd")) {
8306           /* Special kludge for GNU Chess 4 only */
8307           cps->sdKludge = TRUE;
8308           SendTimeControl(cps, movesPerSession, timeControl,
8309                           timeIncrement, appData.searchDepth,
8310                           searchTime);
8311           return;
8312         }
8313         if (!StrStr(message, "llegal")) {
8314             return;
8315         }
8316         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8317             gameMode == IcsIdle) return;
8318         if (forwardMostMove <= backwardMostMove) return;
8319         if (pausing) PauseEvent();
8320       if(appData.forceIllegal) {
8321             // [HGM] illegal: machine refused move; force position after move into it
8322           SendToProgram("force\n", cps);
8323           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8324                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8325                 // when black is to move, while there might be nothing on a2 or black
8326                 // might already have the move. So send the board as if white has the move.
8327                 // But first we must change the stm of the engine, as it refused the last move
8328                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8329                 if(WhiteOnMove(forwardMostMove)) {
8330                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8331                     SendBoard(cps, forwardMostMove); // kludgeless board
8332                 } else {
8333                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8334                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8335                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8336                 }
8337           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8338             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8339                  gameMode == TwoMachinesPlay)
8340               SendToProgram("go\n", cps);
8341             return;
8342       } else
8343         if (gameMode == PlayFromGameFile) {
8344             /* Stop reading this game file */
8345             gameMode = EditGame;
8346             ModeHighlight();
8347         }
8348         /* [HGM] illegal-move claim should forfeit game when Xboard */
8349         /* only passes fully legal moves                            */
8350         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8351             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8352                                 "False illegal-move claim", GE_XBOARD );
8353             return; // do not take back move we tested as valid
8354         }
8355         currentMove = forwardMostMove-1;
8356         DisplayMove(currentMove-1); /* before DisplayMoveError */
8357         SwitchClocks(forwardMostMove-1); // [HGM] race
8358         DisplayBothClocks();
8359         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8360                 parseList[currentMove], _(cps->which));
8361         DisplayMoveError(buf1);
8362         DrawPosition(FALSE, boards[currentMove]);
8363         return;
8364     }
8365     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8366         /* Program has a broken "time" command that
8367            outputs a string not ending in newline.
8368            Don't use it. */
8369         cps->sendTime = 0;
8370     }
8371
8372     /*
8373      * If chess program startup fails, exit with an error message.
8374      * Attempts to recover here are futile.
8375      */
8376     if ((StrStr(message, "unknown host") != NULL)
8377         || (StrStr(message, "No remote directory") != NULL)
8378         || (StrStr(message, "not found") != NULL)
8379         || (StrStr(message, "No such file") != NULL)
8380         || (StrStr(message, "can't alloc") != NULL)
8381         || (StrStr(message, "Permission denied") != NULL)) {
8382
8383         cps->maybeThinking = FALSE;
8384         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8385                 _(cps->which), cps->program, cps->host, message);
8386         RemoveInputSource(cps->isr);
8387         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8388             if(cps == &first) appData.noChessProgram = TRUE;
8389             DisplayError(buf1, 0);
8390         }
8391         return;
8392     }
8393
8394     /*
8395      * Look for hint output
8396      */
8397     if (sscanf(message, "Hint: %s", buf1) == 1) {
8398         if (cps == &first && hintRequested) {
8399             hintRequested = FALSE;
8400             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8401                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8402                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8403                                     PosFlags(forwardMostMove),
8404                                     fromY, fromX, toY, toX, promoChar, buf1);
8405                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8406                 DisplayInformation(buf2);
8407             } else {
8408                 /* Hint move could not be parsed!? */
8409               snprintf(buf2, sizeof(buf2),
8410                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8411                         buf1, _(cps->which));
8412                 DisplayError(buf2, 0);
8413             }
8414         } else {
8415           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8416         }
8417         return;
8418     }
8419
8420     /*
8421      * Ignore other messages if game is not in progress
8422      */
8423     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8424         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8425
8426     /*
8427      * look for win, lose, draw, or draw offer
8428      */
8429     if (strncmp(message, "1-0", 3) == 0) {
8430         char *p, *q, *r = "";
8431         p = strchr(message, '{');
8432         if (p) {
8433             q = strchr(p, '}');
8434             if (q) {
8435                 *q = NULLCHAR;
8436                 r = p + 1;
8437             }
8438         }
8439         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8440         return;
8441     } else if (strncmp(message, "0-1", 3) == 0) {
8442         char *p, *q, *r = "";
8443         p = strchr(message, '{');
8444         if (p) {
8445             q = strchr(p, '}');
8446             if (q) {
8447                 *q = NULLCHAR;
8448                 r = p + 1;
8449             }
8450         }
8451         /* Kludge for Arasan 4.1 bug */
8452         if (strcmp(r, "Black resigns") == 0) {
8453             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8454             return;
8455         }
8456         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8457         return;
8458     } else if (strncmp(message, "1/2", 3) == 0) {
8459         char *p, *q, *r = "";
8460         p = strchr(message, '{');
8461         if (p) {
8462             q = strchr(p, '}');
8463             if (q) {
8464                 *q = NULLCHAR;
8465                 r = p + 1;
8466             }
8467         }
8468
8469         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8470         return;
8471
8472     } else if (strncmp(message, "White resign", 12) == 0) {
8473         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8474         return;
8475     } else if (strncmp(message, "Black resign", 12) == 0) {
8476         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8477         return;
8478     } else if (strncmp(message, "White matches", 13) == 0 ||
8479                strncmp(message, "Black matches", 13) == 0   ) {
8480         /* [HGM] ignore GNUShogi noises */
8481         return;
8482     } else if (strncmp(message, "White", 5) == 0 &&
8483                message[5] != '(' &&
8484                StrStr(message, "Black") == NULL) {
8485         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8486         return;
8487     } else if (strncmp(message, "Black", 5) == 0 &&
8488                message[5] != '(') {
8489         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8490         return;
8491     } else if (strcmp(message, "resign") == 0 ||
8492                strcmp(message, "computer resigns") == 0) {
8493         switch (gameMode) {
8494           case MachinePlaysBlack:
8495           case IcsPlayingBlack:
8496             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8497             break;
8498           case MachinePlaysWhite:
8499           case IcsPlayingWhite:
8500             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8501             break;
8502           case TwoMachinesPlay:
8503             if (cps->twoMachinesColor[0] == 'w')
8504               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8505             else
8506               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8507             break;
8508           default:
8509             /* can't happen */
8510             break;
8511         }
8512         return;
8513     } else if (strncmp(message, "opponent mates", 14) == 0) {
8514         switch (gameMode) {
8515           case MachinePlaysBlack:
8516           case IcsPlayingBlack:
8517             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8518             break;
8519           case MachinePlaysWhite:
8520           case IcsPlayingWhite:
8521             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8522             break;
8523           case TwoMachinesPlay:
8524             if (cps->twoMachinesColor[0] == 'w')
8525               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8526             else
8527               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8528             break;
8529           default:
8530             /* can't happen */
8531             break;
8532         }
8533         return;
8534     } else if (strncmp(message, "computer mates", 14) == 0) {
8535         switch (gameMode) {
8536           case MachinePlaysBlack:
8537           case IcsPlayingBlack:
8538             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8539             break;
8540           case MachinePlaysWhite:
8541           case IcsPlayingWhite:
8542             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8543             break;
8544           case TwoMachinesPlay:
8545             if (cps->twoMachinesColor[0] == 'w')
8546               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8547             else
8548               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8549             break;
8550           default:
8551             /* can't happen */
8552             break;
8553         }
8554         return;
8555     } else if (strncmp(message, "checkmate", 9) == 0) {
8556         if (WhiteOnMove(forwardMostMove)) {
8557             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8558         } else {
8559             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8560         }
8561         return;
8562     } else if (strstr(message, "Draw") != NULL ||
8563                strstr(message, "game is a draw") != NULL) {
8564         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8565         return;
8566     } else if (strstr(message, "offer") != NULL &&
8567                strstr(message, "draw") != NULL) {
8568 #if ZIPPY
8569         if (appData.zippyPlay && first.initDone) {
8570             /* Relay offer to ICS */
8571             SendToICS(ics_prefix);
8572             SendToICS("draw\n");
8573         }
8574 #endif
8575         cps->offeredDraw = 2; /* valid until this engine moves twice */
8576         if (gameMode == TwoMachinesPlay) {
8577             if (cps->other->offeredDraw) {
8578                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8579             /* [HGM] in two-machine mode we delay relaying draw offer      */
8580             /* until after we also have move, to see if it is really claim */
8581             }
8582         } else if (gameMode == MachinePlaysWhite ||
8583                    gameMode == MachinePlaysBlack) {
8584           if (userOfferedDraw) {
8585             DisplayInformation(_("Machine accepts your draw offer"));
8586             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8587           } else {
8588             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8589           }
8590         }
8591     }
8592
8593
8594     /*
8595      * Look for thinking output
8596      */
8597     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8598           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8599                                 ) {
8600         int plylev, mvleft, mvtot, curscore, time;
8601         char mvname[MOVE_LEN];
8602         u64 nodes; // [DM]
8603         char plyext;
8604         int ignore = FALSE;
8605         int prefixHint = FALSE;
8606         mvname[0] = NULLCHAR;
8607
8608         switch (gameMode) {
8609           case MachinePlaysBlack:
8610           case IcsPlayingBlack:
8611             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8612             break;
8613           case MachinePlaysWhite:
8614           case IcsPlayingWhite:
8615             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8616             break;
8617           case AnalyzeMode:
8618           case AnalyzeFile:
8619             break;
8620           case IcsObserving: /* [DM] icsEngineAnalyze */
8621             if (!appData.icsEngineAnalyze) ignore = TRUE;
8622             break;
8623           case TwoMachinesPlay:
8624             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8625                 ignore = TRUE;
8626             }
8627             break;
8628           default:
8629             ignore = TRUE;
8630             break;
8631         }
8632
8633         if (!ignore) {
8634             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8635             buf1[0] = NULLCHAR;
8636             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8637                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8638
8639                 if (plyext != ' ' && plyext != '\t') {
8640                     time *= 100;
8641                 }
8642
8643                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8644                 if( cps->scoreIsAbsolute &&
8645                     ( gameMode == MachinePlaysBlack ||
8646                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8647                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8648                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8649                      !WhiteOnMove(currentMove)
8650                     ) )
8651                 {
8652                     curscore = -curscore;
8653                 }
8654
8655                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8656
8657                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8658                         char buf[MSG_SIZ];
8659                         FILE *f;
8660                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8661                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8662                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8663                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8664                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8665                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8666                                 fclose(f);
8667                         } else DisplayError("failed writing PV", 0);
8668                 }
8669
8670                 tempStats.depth = plylev;
8671                 tempStats.nodes = nodes;
8672                 tempStats.time = time;
8673                 tempStats.score = curscore;
8674                 tempStats.got_only_move = 0;
8675
8676                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8677                         int ticklen;
8678
8679                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8680                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8681                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8682                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8683                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8684                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8685                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8686                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8687                 }
8688
8689                 /* Buffer overflow protection */
8690                 if (pv[0] != NULLCHAR) {
8691                     if (strlen(pv) >= sizeof(tempStats.movelist)
8692                         && appData.debugMode) {
8693                         fprintf(debugFP,
8694                                 "PV is too long; using the first %u bytes.\n",
8695                                 (unsigned) sizeof(tempStats.movelist) - 1);
8696                     }
8697
8698                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8699                 } else {
8700                     sprintf(tempStats.movelist, " no PV\n");
8701                 }
8702
8703                 if (tempStats.seen_stat) {
8704                     tempStats.ok_to_send = 1;
8705                 }
8706
8707                 if (strchr(tempStats.movelist, '(') != NULL) {
8708                     tempStats.line_is_book = 1;
8709                     tempStats.nr_moves = 0;
8710                     tempStats.moves_left = 0;
8711                 } else {
8712                     tempStats.line_is_book = 0;
8713                 }
8714
8715                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8716                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8717
8718                 SendProgramStatsToFrontend( cps, &tempStats );
8719
8720                 /*
8721                     [AS] Protect the thinkOutput buffer from overflow... this
8722                     is only useful if buf1 hasn't overflowed first!
8723                 */
8724                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8725                          plylev,
8726                          (gameMode == TwoMachinesPlay ?
8727                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8728                          ((double) curscore) / 100.0,
8729                          prefixHint ? lastHint : "",
8730                          prefixHint ? " " : "" );
8731
8732                 if( buf1[0] != NULLCHAR ) {
8733                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8734
8735                     if( strlen(pv) > max_len ) {
8736                         if( appData.debugMode) {
8737                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8738                         }
8739                         pv[max_len+1] = '\0';
8740                     }
8741
8742                     strcat( thinkOutput, pv);
8743                 }
8744
8745                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8746                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8747                     DisplayMove(currentMove - 1);
8748                 }
8749                 return;
8750
8751             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8752                 /* crafty (9.25+) says "(only move) <move>"
8753                  * if there is only 1 legal move
8754                  */
8755                 sscanf(p, "(only move) %s", buf1);
8756                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8757                 sprintf(programStats.movelist, "%s (only move)", buf1);
8758                 programStats.depth = 1;
8759                 programStats.nr_moves = 1;
8760                 programStats.moves_left = 1;
8761                 programStats.nodes = 1;
8762                 programStats.time = 1;
8763                 programStats.got_only_move = 1;
8764
8765                 /* Not really, but we also use this member to
8766                    mean "line isn't going to change" (Crafty
8767                    isn't searching, so stats won't change) */
8768                 programStats.line_is_book = 1;
8769
8770                 SendProgramStatsToFrontend( cps, &programStats );
8771
8772                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8773                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8774                     DisplayMove(currentMove - 1);
8775                 }
8776                 return;
8777             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8778                               &time, &nodes, &plylev, &mvleft,
8779                               &mvtot, mvname) >= 5) {
8780                 /* The stat01: line is from Crafty (9.29+) in response
8781                    to the "." command */
8782                 programStats.seen_stat = 1;
8783                 cps->maybeThinking = TRUE;
8784
8785                 if (programStats.got_only_move || !appData.periodicUpdates)
8786                   return;
8787
8788                 programStats.depth = plylev;
8789                 programStats.time = time;
8790                 programStats.nodes = nodes;
8791                 programStats.moves_left = mvleft;
8792                 programStats.nr_moves = mvtot;
8793                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8794                 programStats.ok_to_send = 1;
8795                 programStats.movelist[0] = '\0';
8796
8797                 SendProgramStatsToFrontend( cps, &programStats );
8798
8799                 return;
8800
8801             } else if (strncmp(message,"++",2) == 0) {
8802                 /* Crafty 9.29+ outputs this */
8803                 programStats.got_fail = 2;
8804                 return;
8805
8806             } else if (strncmp(message,"--",2) == 0) {
8807                 /* Crafty 9.29+ outputs this */
8808                 programStats.got_fail = 1;
8809                 return;
8810
8811             } else if (thinkOutput[0] != NULLCHAR &&
8812                        strncmp(message, "    ", 4) == 0) {
8813                 unsigned message_len;
8814
8815                 p = message;
8816                 while (*p && *p == ' ') p++;
8817
8818                 message_len = strlen( p );
8819
8820                 /* [AS] Avoid buffer overflow */
8821                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8822                     strcat(thinkOutput, " ");
8823                     strcat(thinkOutput, p);
8824                 }
8825
8826                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8827                     strcat(programStats.movelist, " ");
8828                     strcat(programStats.movelist, p);
8829                 }
8830
8831                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8832                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8833                     DisplayMove(currentMove - 1);
8834                 }
8835                 return;
8836             }
8837         }
8838         else {
8839             buf1[0] = NULLCHAR;
8840
8841             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8842                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8843             {
8844                 ChessProgramStats cpstats;
8845
8846                 if (plyext != ' ' && plyext != '\t') {
8847                     time *= 100;
8848                 }
8849
8850                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8851                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8852                     curscore = -curscore;
8853                 }
8854
8855                 cpstats.depth = plylev;
8856                 cpstats.nodes = nodes;
8857                 cpstats.time = time;
8858                 cpstats.score = curscore;
8859                 cpstats.got_only_move = 0;
8860                 cpstats.movelist[0] = '\0';
8861
8862                 if (buf1[0] != NULLCHAR) {
8863                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8864                 }
8865
8866                 cpstats.ok_to_send = 0;
8867                 cpstats.line_is_book = 0;
8868                 cpstats.nr_moves = 0;
8869                 cpstats.moves_left = 0;
8870
8871                 SendProgramStatsToFrontend( cps, &cpstats );
8872             }
8873         }
8874     }
8875 }
8876
8877
8878 /* Parse a game score from the character string "game", and
8879    record it as the history of the current game.  The game
8880    score is NOT assumed to start from the standard position.
8881    The display is not updated in any way.
8882    */
8883 void
8884 ParseGameHistory(game)
8885      char *game;
8886 {
8887     ChessMove moveType;
8888     int fromX, fromY, toX, toY, boardIndex;
8889     char promoChar;
8890     char *p, *q;
8891     char buf[MSG_SIZ];
8892
8893     if (appData.debugMode)
8894       fprintf(debugFP, "Parsing game history: %s\n", game);
8895
8896     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8897     gameInfo.site = StrSave(appData.icsHost);
8898     gameInfo.date = PGNDate();
8899     gameInfo.round = StrSave("-");
8900
8901     /* Parse out names of players */
8902     while (*game == ' ') game++;
8903     p = buf;
8904     while (*game != ' ') *p++ = *game++;
8905     *p = NULLCHAR;
8906     gameInfo.white = StrSave(buf);
8907     while (*game == ' ') game++;
8908     p = buf;
8909     while (*game != ' ' && *game != '\n') *p++ = *game++;
8910     *p = NULLCHAR;
8911     gameInfo.black = StrSave(buf);
8912
8913     /* Parse moves */
8914     boardIndex = blackPlaysFirst ? 1 : 0;
8915     yynewstr(game);
8916     for (;;) {
8917         yyboardindex = boardIndex;
8918         moveType = (ChessMove) Myylex();
8919         switch (moveType) {
8920           case IllegalMove:             /* maybe suicide chess, etc. */
8921   if (appData.debugMode) {
8922     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8923     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8924     setbuf(debugFP, NULL);
8925   }
8926           case WhitePromotion:
8927           case BlackPromotion:
8928           case WhiteNonPromotion:
8929           case BlackNonPromotion:
8930           case NormalMove:
8931           case WhiteCapturesEnPassant:
8932           case BlackCapturesEnPassant:
8933           case WhiteKingSideCastle:
8934           case WhiteQueenSideCastle:
8935           case BlackKingSideCastle:
8936           case BlackQueenSideCastle:
8937           case WhiteKingSideCastleWild:
8938           case WhiteQueenSideCastleWild:
8939           case BlackKingSideCastleWild:
8940           case BlackQueenSideCastleWild:
8941           /* PUSH Fabien */
8942           case WhiteHSideCastleFR:
8943           case WhiteASideCastleFR:
8944           case BlackHSideCastleFR:
8945           case BlackASideCastleFR:
8946           /* POP Fabien */
8947             fromX = currentMoveString[0] - AAA;
8948             fromY = currentMoveString[1] - ONE;
8949             toX = currentMoveString[2] - AAA;
8950             toY = currentMoveString[3] - ONE;
8951             promoChar = currentMoveString[4];
8952             break;
8953           case WhiteDrop:
8954           case BlackDrop:
8955             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8956             fromX = moveType == WhiteDrop ?
8957               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8958             (int) CharToPiece(ToLower(currentMoveString[0]));
8959             fromY = DROP_RANK;
8960             toX = currentMoveString[2] - AAA;
8961             toY = currentMoveString[3] - ONE;
8962             promoChar = NULLCHAR;
8963             break;
8964           case AmbiguousMove:
8965             /* bug? */
8966             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8967   if (appData.debugMode) {
8968     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8970     setbuf(debugFP, NULL);
8971   }
8972             DisplayError(buf, 0);
8973             return;
8974           case ImpossibleMove:
8975             /* bug? */
8976             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8977   if (appData.debugMode) {
8978     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8979     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8980     setbuf(debugFP, NULL);
8981   }
8982             DisplayError(buf, 0);
8983             return;
8984           case EndOfFile:
8985             if (boardIndex < backwardMostMove) {
8986                 /* Oops, gap.  How did that happen? */
8987                 DisplayError(_("Gap in move list"), 0);
8988                 return;
8989             }
8990             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8991             if (boardIndex > forwardMostMove) {
8992                 forwardMostMove = boardIndex;
8993             }
8994             return;
8995           case ElapsedTime:
8996             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8997                 strcat(parseList[boardIndex-1], " ");
8998                 strcat(parseList[boardIndex-1], yy_text);
8999             }
9000             continue;
9001           case Comment:
9002           case PGNTag:
9003           case NAG:
9004           default:
9005             /* ignore */
9006             continue;
9007           case WhiteWins:
9008           case BlackWins:
9009           case GameIsDrawn:
9010           case GameUnfinished:
9011             if (gameMode == IcsExamining) {
9012                 if (boardIndex < backwardMostMove) {
9013                     /* Oops, gap.  How did that happen? */
9014                     return;
9015                 }
9016                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9017                 return;
9018             }
9019             gameInfo.result = moveType;
9020             p = strchr(yy_text, '{');
9021             if (p == NULL) p = strchr(yy_text, '(');
9022             if (p == NULL) {
9023                 p = yy_text;
9024                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9025             } else {
9026                 q = strchr(p, *p == '{' ? '}' : ')');
9027                 if (q != NULL) *q = NULLCHAR;
9028                 p++;
9029             }
9030             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9031             gameInfo.resultDetails = StrSave(p);
9032             continue;
9033         }
9034         if (boardIndex >= forwardMostMove &&
9035             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9036             backwardMostMove = blackPlaysFirst ? 1 : 0;
9037             return;
9038         }
9039         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9040                                  fromY, fromX, toY, toX, promoChar,
9041                                  parseList[boardIndex]);
9042         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9043         /* currentMoveString is set as a side-effect of yylex */
9044         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9045         strcat(moveList[boardIndex], "\n");
9046         boardIndex++;
9047         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9048         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9049           case MT_NONE:
9050           case MT_STALEMATE:
9051           default:
9052             break;
9053           case MT_CHECK:
9054             if(gameInfo.variant != VariantShogi)
9055                 strcat(parseList[boardIndex - 1], "+");
9056             break;
9057           case MT_CHECKMATE:
9058           case MT_STAINMATE:
9059             strcat(parseList[boardIndex - 1], "#");
9060             break;
9061         }
9062     }
9063 }
9064
9065
9066 /* Apply a move to the given board  */
9067 void
9068 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9069      int fromX, fromY, toX, toY;
9070      int promoChar;
9071      Board board;
9072 {
9073   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9074   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9075
9076     /* [HGM] compute & store e.p. status and castling rights for new position */
9077     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9078
9079       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9080       oldEP = (signed char)board[EP_STATUS];
9081       board[EP_STATUS] = EP_NONE;
9082
9083   if (fromY == DROP_RANK) {
9084         /* must be first */
9085         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9086             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9087             return;
9088         }
9089         piece = board[toY][toX] = (ChessSquare) fromX;
9090   } else {
9091       int i;
9092
9093       if( board[toY][toX] != EmptySquare )
9094            board[EP_STATUS] = EP_CAPTURE;
9095
9096       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9097            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9098                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9099       } else
9100       if( board[fromY][fromX] == WhitePawn ) {
9101            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9102                board[EP_STATUS] = EP_PAWN_MOVE;
9103            if( toY-fromY==2) {
9104                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9105                         gameInfo.variant != VariantBerolina || toX < fromX)
9106                       board[EP_STATUS] = toX | berolina;
9107                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9108                         gameInfo.variant != VariantBerolina || toX > fromX)
9109                       board[EP_STATUS] = toX;
9110            }
9111       } else
9112       if( board[fromY][fromX] == BlackPawn ) {
9113            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9114                board[EP_STATUS] = EP_PAWN_MOVE;
9115            if( toY-fromY== -2) {
9116                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9117                         gameInfo.variant != VariantBerolina || toX < fromX)
9118                       board[EP_STATUS] = toX | berolina;
9119                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9120                         gameInfo.variant != VariantBerolina || toX > fromX)
9121                       board[EP_STATUS] = toX;
9122            }
9123        }
9124
9125        for(i=0; i<nrCastlingRights; i++) {
9126            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9127               board[CASTLING][i] == toX   && castlingRank[i] == toY
9128              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9129        }
9130
9131      if (fromX == toX && fromY == toY) return;
9132
9133      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9134      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9135      if(gameInfo.variant == VariantKnightmate)
9136          king += (int) WhiteUnicorn - (int) WhiteKing;
9137
9138     /* Code added by Tord: */
9139     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9140     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9141         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9142       board[fromY][fromX] = EmptySquare;
9143       board[toY][toX] = EmptySquare;
9144       if((toX > fromX) != (piece == WhiteRook)) {
9145         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9146       } else {
9147         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9148       }
9149     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9150                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9151       board[fromY][fromX] = EmptySquare;
9152       board[toY][toX] = EmptySquare;
9153       if((toX > fromX) != (piece == BlackRook)) {
9154         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9155       } else {
9156         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9157       }
9158     /* End of code added by Tord */
9159
9160     } else if (board[fromY][fromX] == king
9161         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9162         && toY == fromY && toX > fromX+1) {
9163         board[fromY][fromX] = EmptySquare;
9164         board[toY][toX] = king;
9165         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9166         board[fromY][BOARD_RGHT-1] = EmptySquare;
9167     } else if (board[fromY][fromX] == king
9168         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9169                && toY == fromY && toX < fromX-1) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = king;
9172         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9173         board[fromY][BOARD_LEFT] = EmptySquare;
9174     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9175                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9176                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9177                ) {
9178         /* white pawn promotion */
9179         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9180         if(gameInfo.variant==VariantBughouse ||
9181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9183         board[fromY][fromX] = EmptySquare;
9184     } else if ((fromY >= BOARD_HEIGHT>>1)
9185                && (toX != fromX)
9186                && gameInfo.variant != VariantXiangqi
9187                && gameInfo.variant != VariantBerolina
9188                && (board[fromY][fromX] == WhitePawn)
9189                && (board[toY][toX] == EmptySquare)) {
9190         board[fromY][fromX] = EmptySquare;
9191         board[toY][toX] = WhitePawn;
9192         captured = board[toY - 1][toX];
9193         board[toY - 1][toX] = EmptySquare;
9194     } else if ((fromY == BOARD_HEIGHT-4)
9195                && (toX == fromX)
9196                && gameInfo.variant == VariantBerolina
9197                && (board[fromY][fromX] == WhitePawn)
9198                && (board[toY][toX] == EmptySquare)) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = WhitePawn;
9201         if(oldEP & EP_BEROLIN_A) {
9202                 captured = board[fromY][fromX-1];
9203                 board[fromY][fromX-1] = EmptySquare;
9204         }else{  captured = board[fromY][fromX+1];
9205                 board[fromY][fromX+1] = EmptySquare;
9206         }
9207     } else if (board[fromY][fromX] == king
9208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9209                && toY == fromY && toX > fromX+1) {
9210         board[fromY][fromX] = EmptySquare;
9211         board[toY][toX] = king;
9212         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9213         board[fromY][BOARD_RGHT-1] = EmptySquare;
9214     } else if (board[fromY][fromX] == king
9215         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9216                && toY == fromY && toX < fromX-1) {
9217         board[fromY][fromX] = EmptySquare;
9218         board[toY][toX] = king;
9219         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9220         board[fromY][BOARD_LEFT] = EmptySquare;
9221     } else if (fromY == 7 && fromX == 3
9222                && board[fromY][fromX] == BlackKing
9223                && toY == 7 && toX == 5) {
9224         board[fromY][fromX] = EmptySquare;
9225         board[toY][toX] = BlackKing;
9226         board[fromY][7] = EmptySquare;
9227         board[toY][4] = BlackRook;
9228     } else if (fromY == 7 && fromX == 3
9229                && board[fromY][fromX] == BlackKing
9230                && toY == 7 && toX == 1) {
9231         board[fromY][fromX] = EmptySquare;
9232         board[toY][toX] = BlackKing;
9233         board[fromY][0] = EmptySquare;
9234         board[toY][2] = BlackRook;
9235     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9236                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9237                && toY < promoRank && promoChar
9238                ) {
9239         /* black pawn promotion */
9240         board[toY][toX] = CharToPiece(ToLower(promoChar));
9241         if(gameInfo.variant==VariantBughouse ||
9242            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9243             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9244         board[fromY][fromX] = EmptySquare;
9245     } else if ((fromY < BOARD_HEIGHT>>1)
9246                && (toX != fromX)
9247                && gameInfo.variant != VariantXiangqi
9248                && gameInfo.variant != VariantBerolina
9249                && (board[fromY][fromX] == BlackPawn)
9250                && (board[toY][toX] == EmptySquare)) {
9251         board[fromY][fromX] = EmptySquare;
9252         board[toY][toX] = BlackPawn;
9253         captured = board[toY + 1][toX];
9254         board[toY + 1][toX] = EmptySquare;
9255     } else if ((fromY == 3)
9256                && (toX == fromX)
9257                && gameInfo.variant == VariantBerolina
9258                && (board[fromY][fromX] == BlackPawn)
9259                && (board[toY][toX] == EmptySquare)) {
9260         board[fromY][fromX] = EmptySquare;
9261         board[toY][toX] = BlackPawn;
9262         if(oldEP & EP_BEROLIN_A) {
9263                 captured = board[fromY][fromX-1];
9264                 board[fromY][fromX-1] = EmptySquare;
9265         }else{  captured = board[fromY][fromX+1];
9266                 board[fromY][fromX+1] = EmptySquare;
9267         }
9268     } else {
9269         board[toY][toX] = board[fromY][fromX];
9270         board[fromY][fromX] = EmptySquare;
9271     }
9272   }
9273
9274     if (gameInfo.holdingsWidth != 0) {
9275
9276       /* !!A lot more code needs to be written to support holdings  */
9277       /* [HGM] OK, so I have written it. Holdings are stored in the */
9278       /* penultimate board files, so they are automaticlly stored   */
9279       /* in the game history.                                       */
9280       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9281                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9282         /* Delete from holdings, by decreasing count */
9283         /* and erasing image if necessary            */
9284         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9285         if(p < (int) BlackPawn) { /* white drop */
9286              p -= (int)WhitePawn;
9287                  p = PieceToNumber((ChessSquare)p);
9288              if(p >= gameInfo.holdingsSize) p = 0;
9289              if(--board[p][BOARD_WIDTH-2] <= 0)
9290                   board[p][BOARD_WIDTH-1] = EmptySquare;
9291              if((int)board[p][BOARD_WIDTH-2] < 0)
9292                         board[p][BOARD_WIDTH-2] = 0;
9293         } else {                  /* black drop */
9294              p -= (int)BlackPawn;
9295                  p = PieceToNumber((ChessSquare)p);
9296              if(p >= gameInfo.holdingsSize) p = 0;
9297              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9298                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9299              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9300                         board[BOARD_HEIGHT-1-p][1] = 0;
9301         }
9302       }
9303       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9304           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9305         /* [HGM] holdings: Add to holdings, if holdings exist */
9306         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9307                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9308                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9309         }
9310         p = (int) captured;
9311         if (p >= (int) BlackPawn) {
9312           p -= (int)BlackPawn;
9313           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9314                   /* in Shogi restore piece to its original  first */
9315                   captured = (ChessSquare) (DEMOTED captured);
9316                   p = DEMOTED p;
9317           }
9318           p = PieceToNumber((ChessSquare)p);
9319           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9320           board[p][BOARD_WIDTH-2]++;
9321           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9322         } else {
9323           p -= (int)WhitePawn;
9324           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9325                   captured = (ChessSquare) (DEMOTED captured);
9326                   p = DEMOTED p;
9327           }
9328           p = PieceToNumber((ChessSquare)p);
9329           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9330           board[BOARD_HEIGHT-1-p][1]++;
9331           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9332         }
9333       }
9334     } else if (gameInfo.variant == VariantAtomic) {
9335       if (captured != EmptySquare) {
9336         int y, x;
9337         for (y = toY-1; y <= toY+1; y++) {
9338           for (x = toX-1; x <= toX+1; x++) {
9339             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9340                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9341               board[y][x] = EmptySquare;
9342             }
9343           }
9344         }
9345         board[toY][toX] = EmptySquare;
9346       }
9347     }
9348     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9349         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9350     } else
9351     if(promoChar == '+') {
9352         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9353         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9354     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9355         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9356     }
9357     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9358                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9359         // [HGM] superchess: take promotion piece out of holdings
9360         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9361         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9362             if(!--board[k][BOARD_WIDTH-2])
9363                 board[k][BOARD_WIDTH-1] = EmptySquare;
9364         } else {
9365             if(!--board[BOARD_HEIGHT-1-k][1])
9366                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9367         }
9368     }
9369
9370 }
9371
9372 /* Updates forwardMostMove */
9373 void
9374 MakeMove(fromX, fromY, toX, toY, promoChar)
9375      int fromX, fromY, toX, toY;
9376      int promoChar;
9377 {
9378 //    forwardMostMove++; // [HGM] bare: moved downstream
9379
9380     (void) CoordsToAlgebraic(boards[forwardMostMove],
9381                              PosFlags(forwardMostMove),
9382                              fromY, fromX, toY, toX, promoChar,
9383                              parseList[forwardMostMove]);
9384
9385     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9386         int timeLeft; static int lastLoadFlag=0; int king, piece;
9387         piece = boards[forwardMostMove][fromY][fromX];
9388         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9389         if(gameInfo.variant == VariantKnightmate)
9390             king += (int) WhiteUnicorn - (int) WhiteKing;
9391         if(forwardMostMove == 0) {
9392             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9393                 fprintf(serverMoves, "%s;", UserName());
9394             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9395                 fprintf(serverMoves, "%s;", second.tidy);
9396             fprintf(serverMoves, "%s;", first.tidy);
9397             if(gameMode == MachinePlaysWhite)
9398                 fprintf(serverMoves, "%s;", UserName());
9399             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9400                 fprintf(serverMoves, "%s;", second.tidy);
9401         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9402         lastLoadFlag = loadFlag;
9403         // print base move
9404         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9405         // print castling suffix
9406         if( toY == fromY && piece == king ) {
9407             if(toX-fromX > 1)
9408                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9409             if(fromX-toX >1)
9410                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9411         }
9412         // e.p. suffix
9413         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9414              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9415              boards[forwardMostMove][toY][toX] == EmptySquare
9416              && fromX != toX && fromY != toY)
9417                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9418         // promotion suffix
9419         if(promoChar != NULLCHAR)
9420                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9421         if(!loadFlag) {
9422                 char buf[MOVE_LEN*2], *p; int len;
9423             fprintf(serverMoves, "/%d/%d",
9424                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9425             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9426             else                      timeLeft = blackTimeRemaining/1000;
9427             fprintf(serverMoves, "/%d", timeLeft);
9428                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9429                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9430                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9431             fprintf(serverMoves, "/%s", buf);
9432         }
9433         fflush(serverMoves);
9434     }
9435
9436     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9437         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9438       return;
9439     }
9440     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9441     if (commentList[forwardMostMove+1] != NULL) {
9442         free(commentList[forwardMostMove+1]);
9443         commentList[forwardMostMove+1] = NULL;
9444     }
9445     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9446     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9447     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9448     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9449     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9450     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9451     adjustedClock = FALSE;
9452     gameInfo.result = GameUnfinished;
9453     if (gameInfo.resultDetails != NULL) {
9454         free(gameInfo.resultDetails);
9455         gameInfo.resultDetails = NULL;
9456     }
9457     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9458                               moveList[forwardMostMove - 1]);
9459     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9460       case MT_NONE:
9461       case MT_STALEMATE:
9462       default:
9463         break;
9464       case MT_CHECK:
9465         if(gameInfo.variant != VariantShogi)
9466             strcat(parseList[forwardMostMove - 1], "+");
9467         break;
9468       case MT_CHECKMATE:
9469       case MT_STAINMATE:
9470         strcat(parseList[forwardMostMove - 1], "#");
9471         break;
9472     }
9473     if (appData.debugMode) {
9474         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9475     }
9476
9477 }
9478
9479 /* Updates currentMove if not pausing */
9480 void
9481 ShowMove(fromX, fromY, toX, toY)
9482 {
9483     int instant = (gameMode == PlayFromGameFile) ?
9484         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9485     if(appData.noGUI) return;
9486     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9487         if (!instant) {
9488             if (forwardMostMove == currentMove + 1) {
9489                 AnimateMove(boards[forwardMostMove - 1],
9490                             fromX, fromY, toX, toY);
9491             }
9492             if (appData.highlightLastMove) {
9493                 SetHighlights(fromX, fromY, toX, toY);
9494             }
9495         }
9496         currentMove = forwardMostMove;
9497     }
9498
9499     if (instant) return;
9500
9501     DisplayMove(currentMove - 1);
9502     DrawPosition(FALSE, boards[currentMove]);
9503     DisplayBothClocks();
9504     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9505 }
9506
9507 void SendEgtPath(ChessProgramState *cps)
9508 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9509         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9510
9511         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9512
9513         while(*p) {
9514             char c, *q = name+1, *r, *s;
9515
9516             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9517             while(*p && *p != ',') *q++ = *p++;
9518             *q++ = ':'; *q = 0;
9519             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9520                 strcmp(name, ",nalimov:") == 0 ) {
9521                 // take nalimov path from the menu-changeable option first, if it is defined
9522               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9523                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9524             } else
9525             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9526                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9527                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9528                 s = r = StrStr(s, ":") + 1; // beginning of path info
9529                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9530                 c = *r; *r = 0;             // temporarily null-terminate path info
9531                     *--q = 0;               // strip of trailig ':' from name
9532                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9533                 *r = c;
9534                 SendToProgram(buf,cps);     // send egtbpath command for this format
9535             }
9536             if(*p == ',') p++; // read away comma to position for next format name
9537         }
9538 }
9539
9540 void
9541 InitChessProgram(cps, setup)
9542      ChessProgramState *cps;
9543      int setup; /* [HGM] needed to setup FRC opening position */
9544 {
9545     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9546     if (appData.noChessProgram) return;
9547     hintRequested = FALSE;
9548     bookRequested = FALSE;
9549
9550     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9551     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9552     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9553     if(cps->memSize) { /* [HGM] memory */
9554       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9555         SendToProgram(buf, cps);
9556     }
9557     SendEgtPath(cps); /* [HGM] EGT */
9558     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9559       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9560         SendToProgram(buf, cps);
9561     }
9562
9563     SendToProgram(cps->initString, cps);
9564     if (gameInfo.variant != VariantNormal &&
9565         gameInfo.variant != VariantLoadable
9566         /* [HGM] also send variant if board size non-standard */
9567         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9568                                             ) {
9569       char *v = VariantName(gameInfo.variant);
9570       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9571         /* [HGM] in protocol 1 we have to assume all variants valid */
9572         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9573         DisplayFatalError(buf, 0, 1);
9574         return;
9575       }
9576
9577       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9578       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9579       if( gameInfo.variant == VariantXiangqi )
9580            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9581       if( gameInfo.variant == VariantShogi )
9582            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9583       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9584            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9585       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9586           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9587            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9588       if( gameInfo.variant == VariantCourier )
9589            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9590       if( gameInfo.variant == VariantSuper )
9591            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9592       if( gameInfo.variant == VariantGreat )
9593            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9594       if( gameInfo.variant == VariantSChess )
9595            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9596       if( gameInfo.variant == VariantGrand )
9597            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9598
9599       if(overruled) {
9600         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9601                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9602            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9603            if(StrStr(cps->variants, b) == NULL) {
9604                // specific sized variant not known, check if general sizing allowed
9605                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9606                    if(StrStr(cps->variants, "boardsize") == NULL) {
9607                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9608                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9609                        DisplayFatalError(buf, 0, 1);
9610                        return;
9611                    }
9612                    /* [HGM] here we really should compare with the maximum supported board size */
9613                }
9614            }
9615       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9616       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9617       SendToProgram(buf, cps);
9618     }
9619     currentlyInitializedVariant = gameInfo.variant;
9620
9621     /* [HGM] send opening position in FRC to first engine */
9622     if(setup) {
9623           SendToProgram("force\n", cps);
9624           SendBoard(cps, 0);
9625           /* engine is now in force mode! Set flag to wake it up after first move. */
9626           setboardSpoiledMachineBlack = 1;
9627     }
9628
9629     if (cps->sendICS) {
9630       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9631       SendToProgram(buf, cps);
9632     }
9633     cps->maybeThinking = FALSE;
9634     cps->offeredDraw = 0;
9635     if (!appData.icsActive) {
9636         SendTimeControl(cps, movesPerSession, timeControl,
9637                         timeIncrement, appData.searchDepth,
9638                         searchTime);
9639     }
9640     if (appData.showThinking
9641         // [HGM] thinking: four options require thinking output to be sent
9642         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9643                                 ) {
9644         SendToProgram("post\n", cps);
9645     }
9646     SendToProgram("hard\n", cps);
9647     if (!appData.ponderNextMove) {
9648         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9649            it without being sure what state we are in first.  "hard"
9650            is not a toggle, so that one is OK.
9651          */
9652         SendToProgram("easy\n", cps);
9653     }
9654     if (cps->usePing) {
9655       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9656       SendToProgram(buf, cps);
9657     }
9658     cps->initDone = TRUE;
9659     ClearEngineOutputPane(cps == &second);
9660 }
9661
9662
9663 void
9664 StartChessProgram(cps)
9665      ChessProgramState *cps;
9666 {
9667     char buf[MSG_SIZ];
9668     int err;
9669
9670     if (appData.noChessProgram) return;
9671     cps->initDone = FALSE;
9672
9673     if (strcmp(cps->host, "localhost") == 0) {
9674         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9675     } else if (*appData.remoteShell == NULLCHAR) {
9676         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9677     } else {
9678         if (*appData.remoteUser == NULLCHAR) {
9679           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9680                     cps->program);
9681         } else {
9682           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9683                     cps->host, appData.remoteUser, cps->program);
9684         }
9685         err = StartChildProcess(buf, "", &cps->pr);
9686     }
9687
9688     if (err != 0) {
9689       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9690         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9691         if(cps != &first) return;
9692         appData.noChessProgram = TRUE;
9693         ThawUI();
9694         SetNCPMode();
9695 //      DisplayFatalError(buf, err, 1);
9696 //      cps->pr = NoProc;
9697 //      cps->isr = NULL;
9698         return;
9699     }
9700
9701     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9702     if (cps->protocolVersion > 1) {
9703       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9704       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9705       cps->comboCnt = 0;  //                and values of combo boxes
9706       SendToProgram(buf, cps);
9707     } else {
9708       SendToProgram("xboard\n", cps);
9709     }
9710 }
9711
9712 void
9713 TwoMachinesEventIfReady P((void))
9714 {
9715   static int curMess = 0;
9716   if (first.lastPing != first.lastPong) {
9717     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9718     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9719     return;
9720   }
9721   if (second.lastPing != second.lastPong) {
9722     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9723     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9724     return;
9725   }
9726   DisplayMessage("", ""); curMess = 0;
9727   ThawUI();
9728   TwoMachinesEvent();
9729 }
9730
9731 char *
9732 MakeName(char *template)
9733 {
9734     time_t clock;
9735     struct tm *tm;
9736     static char buf[MSG_SIZ];
9737     char *p = buf;
9738     int i;
9739
9740     clock = time((time_t *)NULL);
9741     tm = localtime(&clock);
9742
9743     while(*p++ = *template++) if(p[-1] == '%') {
9744         switch(*template++) {
9745           case 0:   *p = 0; return buf;
9746           case 'Y': i = tm->tm_year+1900; break;
9747           case 'y': i = tm->tm_year-100; break;
9748           case 'M': i = tm->tm_mon+1; break;
9749           case 'd': i = tm->tm_mday; break;
9750           case 'h': i = tm->tm_hour; break;
9751           case 'm': i = tm->tm_min; break;
9752           case 's': i = tm->tm_sec; break;
9753           default:  i = 0;
9754         }
9755         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9756     }
9757     return buf;
9758 }
9759
9760 int
9761 CountPlayers(char *p)
9762 {
9763     int n = 0;
9764     while(p = strchr(p, '\n')) p++, n++; // count participants
9765     return n;
9766 }
9767
9768 FILE *
9769 WriteTourneyFile(char *results, FILE *f)
9770 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9771     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9772     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9773         // create a file with tournament description
9774         fprintf(f, "-participants {%s}\n", appData.participants);
9775         fprintf(f, "-seedBase %d\n", appData.seedBase);
9776         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9777         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9778         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9779         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9780         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9781         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9782         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9783         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9784         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9785         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9786         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9787         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9788         if(searchTime > 0)
9789                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9790         else {
9791                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9792                 fprintf(f, "-tc %s\n", appData.timeControl);
9793                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9794         }
9795         fprintf(f, "-results \"%s\"\n", results);
9796     }
9797     return f;
9798 }
9799
9800 #define MAXENGINES 1000
9801 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9802
9803 void Substitute(char *participants, int expunge)
9804 {
9805     int i, changed, changes=0, nPlayers=0;
9806     char *p, *q, *r, buf[MSG_SIZ];
9807     if(participants == NULL) return;
9808     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9809     r = p = participants; q = appData.participants;
9810     while(*p && *p == *q) {
9811         if(*p == '\n') r = p+1, nPlayers++;
9812         p++; q++;
9813     }
9814     if(*p) { // difference
9815         while(*p && *p++ != '\n');
9816         while(*q && *q++ != '\n');
9817       changed = nPlayers;
9818         changes = 1 + (strcmp(p, q) != 0);
9819     }
9820     if(changes == 1) { // a single engine mnemonic was changed
9821         q = r; while(*q) nPlayers += (*q++ == '\n');
9822         p = buf; while(*r && (*p = *r++) != '\n') p++;
9823         *p = NULLCHAR;
9824         NamesToList(firstChessProgramNames, command, mnemonic);
9825         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9826         if(mnemonic[i]) { // The substitute is valid
9827             FILE *f;
9828             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9829                 flock(fileno(f), LOCK_EX);
9830                 ParseArgsFromFile(f);
9831                 fseek(f, 0, SEEK_SET);
9832                 FREE(appData.participants); appData.participants = participants;
9833                 if(expunge) { // erase results of replaced engine
9834                     int len = strlen(appData.results), w, b, dummy;
9835                     for(i=0; i<len; i++) {
9836                         Pairing(i, nPlayers, &w, &b, &dummy);
9837                         if((w == changed || b == changed) && appData.results[i] == '*') {
9838                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9839                             fclose(f);
9840                             return;
9841                         }
9842                     }
9843                     for(i=0; i<len; i++) {
9844                         Pairing(i, nPlayers, &w, &b, &dummy);
9845                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9846                     }
9847                 }
9848                 WriteTourneyFile(appData.results, f);
9849                 fclose(f); // release lock
9850                 return;
9851             }
9852         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9853     }
9854     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9855     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9856     free(participants);
9857     return;
9858 }
9859
9860 int
9861 CreateTourney(char *name)
9862 {
9863         FILE *f;
9864         if(matchMode && strcmp(name, appData.tourneyFile)) {
9865              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9866         }
9867         if(name[0] == NULLCHAR) {
9868             if(appData.participants[0])
9869                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9870             return 0;
9871         }
9872         f = fopen(name, "r");
9873         if(f) { // file exists
9874             ASSIGN(appData.tourneyFile, name);
9875             ParseArgsFromFile(f); // parse it
9876         } else {
9877             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9878             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9879                 DisplayError(_("Not enough participants"), 0);
9880                 return 0;
9881             }
9882             ASSIGN(appData.tourneyFile, name);
9883             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9884             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9885         }
9886         fclose(f);
9887         appData.noChessProgram = FALSE;
9888         appData.clockMode = TRUE;
9889         SetGNUMode();
9890         return 1;
9891 }
9892
9893 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9894 {
9895     char buf[MSG_SIZ], *p, *q;
9896     int i=1;
9897     while(*names) {
9898         p = names; q = buf;
9899         while(*p && *p != '\n') *q++ = *p++;
9900         *q = 0;
9901         if(engineList[i]) free(engineList[i]);
9902         engineList[i] = strdup(buf);
9903         if(*p == '\n') p++;
9904         TidyProgramName(engineList[i], "localhost", buf);
9905         if(engineMnemonic[i]) free(engineMnemonic[i]);
9906         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9907             strcat(buf, " (");
9908             sscanf(q + 8, "%s", buf + strlen(buf));
9909             strcat(buf, ")");
9910         }
9911         engineMnemonic[i] = strdup(buf);
9912         names = p; i++;
9913       if(i > MAXENGINES - 2) break;
9914     }
9915     engineList[i] = engineMnemonic[i] = NULL;
9916 }
9917
9918 // following implemented as macro to avoid type limitations
9919 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9920
9921 void SwapEngines(int n)
9922 {   // swap settings for first engine and other engine (so far only some selected options)
9923     int h;
9924     char *p;
9925     if(n == 0) return;
9926     SWAP(directory, p)
9927     SWAP(chessProgram, p)
9928     SWAP(isUCI, h)
9929     SWAP(hasOwnBookUCI, h)
9930     SWAP(protocolVersion, h)
9931     SWAP(reuse, h)
9932     SWAP(scoreIsAbsolute, h)
9933     SWAP(timeOdds, h)
9934     SWAP(logo, p)
9935     SWAP(pgnName, p)
9936     SWAP(pvSAN, h)
9937     SWAP(engOptions, p)
9938 }
9939
9940 void
9941 SetPlayer(int player)
9942 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9943     int i;
9944     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9945     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9946     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9947     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9948     if(mnemonic[i]) {
9949         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9950         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9951         appData.firstHasOwnBookUCI = !appData.defNoBook;
9952         ParseArgsFromString(buf);
9953     }
9954     free(engineName);
9955 }
9956
9957 int
9958 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9959 {   // determine players from game number
9960     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9961
9962     if(appData.tourneyType == 0) {
9963         roundsPerCycle = (nPlayers - 1) | 1;
9964         pairingsPerRound = nPlayers / 2;
9965     } else if(appData.tourneyType > 0) {
9966         roundsPerCycle = nPlayers - appData.tourneyType;
9967         pairingsPerRound = appData.tourneyType;
9968     }
9969     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9970     gamesPerCycle = gamesPerRound * roundsPerCycle;
9971     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9972     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9973     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9974     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9975     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9976     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9977
9978     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9979     if(appData.roundSync) *syncInterval = gamesPerRound;
9980
9981     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9982
9983     if(appData.tourneyType == 0) {
9984         if(curPairing == (nPlayers-1)/2 ) {
9985             *whitePlayer = curRound;
9986             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9987         } else {
9988             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9989             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9990             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9991             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9992         }
9993     } else if(appData.tourneyType > 0) {
9994         *whitePlayer = curPairing;
9995         *blackPlayer = curRound + appData.tourneyType;
9996     }
9997
9998     // take care of white/black alternation per round. 
9999     // For cycles and games this is already taken care of by default, derived from matchGame!
10000     return curRound & 1;
10001 }
10002
10003 int
10004 NextTourneyGame(int nr, int *swapColors)
10005 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10006     char *p, *q;
10007     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10008     FILE *tf;
10009     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10010     tf = fopen(appData.tourneyFile, "r");
10011     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10012     ParseArgsFromFile(tf); fclose(tf);
10013     InitTimeControls(); // TC might be altered from tourney file
10014
10015     nPlayers = CountPlayers(appData.participants); // count participants
10016     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10017     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10018
10019     if(syncInterval) {
10020         p = q = appData.results;
10021         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10022         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10023             DisplayMessage(_("Waiting for other game(s)"),"");
10024             waitingForGame = TRUE;
10025             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10026             return 0;
10027         }
10028         waitingForGame = FALSE;
10029     }
10030
10031     if(appData.tourneyType < 0) {
10032         if(nr>=0 && !pairingReceived) {
10033             char buf[1<<16];
10034             if(pairing.pr == NoProc) {
10035                 if(!appData.pairingEngine[0]) {
10036                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10037                     return 0;
10038                 }
10039                 StartChessProgram(&pairing); // starts the pairing engine
10040             }
10041             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10042             SendToProgram(buf, &pairing);
10043             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10044             SendToProgram(buf, &pairing);
10045             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10046         }
10047         pairingReceived = 0;                              // ... so we continue here 
10048         *swapColors = 0;
10049         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10050         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10051         matchGame = 1; roundNr = nr / syncInterval + 1;
10052     }
10053
10054     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10055
10056     // redefine engines, engine dir, etc.
10057     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10058     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10059     SwapEngines(1);
10060     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10061     SwapEngines(1);         // and make that valid for second engine by swapping
10062     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10063     InitEngine(&second, 1);
10064     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10065     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10066     return 1;
10067 }
10068
10069 void
10070 NextMatchGame()
10071 {   // performs game initialization that does not invoke engines, and then tries to start the game
10072     int res, firstWhite, swapColors = 0;
10073     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10074     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10075     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10076     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10077     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10078     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10079     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10080     Reset(FALSE, first.pr != NoProc);
10081     res = LoadGameOrPosition(matchGame); // setup game
10082     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10083     if(!res) return; // abort when bad game/pos file
10084     TwoMachinesEvent();
10085 }
10086
10087 void UserAdjudicationEvent( int result )
10088 {
10089     ChessMove gameResult = GameIsDrawn;
10090
10091     if( result > 0 ) {
10092         gameResult = WhiteWins;
10093     }
10094     else if( result < 0 ) {
10095         gameResult = BlackWins;
10096     }
10097
10098     if( gameMode == TwoMachinesPlay ) {
10099         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10100     }
10101 }
10102
10103
10104 // [HGM] save: calculate checksum of game to make games easily identifiable
10105 int StringCheckSum(char *s)
10106 {
10107         int i = 0;
10108         if(s==NULL) return 0;
10109         while(*s) i = i*259 + *s++;
10110         return i;
10111 }
10112
10113 int GameCheckSum()
10114 {
10115         int i, sum=0;
10116         for(i=backwardMostMove; i<forwardMostMove; i++) {
10117                 sum += pvInfoList[i].depth;
10118                 sum += StringCheckSum(parseList[i]);
10119                 sum += StringCheckSum(commentList[i]);
10120                 sum *= 261;
10121         }
10122         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10123         return sum + StringCheckSum(commentList[i]);
10124 } // end of save patch
10125
10126 void
10127 GameEnds(result, resultDetails, whosays)
10128      ChessMove result;
10129      char *resultDetails;
10130      int whosays;
10131 {
10132     GameMode nextGameMode;
10133     int isIcsGame;
10134     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10135
10136     if(endingGame) return; /* [HGM] crash: forbid recursion */
10137     endingGame = 1;
10138     if(twoBoards) { // [HGM] dual: switch back to one board
10139         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10140         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10141     }
10142     if (appData.debugMode) {
10143       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10144               result, resultDetails ? resultDetails : "(null)", whosays);
10145     }
10146
10147     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10148
10149     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10150         /* If we are playing on ICS, the server decides when the
10151            game is over, but the engine can offer to draw, claim
10152            a draw, or resign.
10153          */
10154 #if ZIPPY
10155         if (appData.zippyPlay && first.initDone) {
10156             if (result == GameIsDrawn) {
10157                 /* In case draw still needs to be claimed */
10158                 SendToICS(ics_prefix);
10159                 SendToICS("draw\n");
10160             } else if (StrCaseStr(resultDetails, "resign")) {
10161                 SendToICS(ics_prefix);
10162                 SendToICS("resign\n");
10163             }
10164         }
10165 #endif
10166         endingGame = 0; /* [HGM] crash */
10167         return;
10168     }
10169
10170     /* If we're loading the game from a file, stop */
10171     if (whosays == GE_FILE) {
10172       (void) StopLoadGameTimer();
10173       gameFileFP = NULL;
10174     }
10175
10176     /* Cancel draw offers */
10177     first.offeredDraw = second.offeredDraw = 0;
10178
10179     /* If this is an ICS game, only ICS can really say it's done;
10180        if not, anyone can. */
10181     isIcsGame = (gameMode == IcsPlayingWhite ||
10182                  gameMode == IcsPlayingBlack ||
10183                  gameMode == IcsObserving    ||
10184                  gameMode == IcsExamining);
10185
10186     if (!isIcsGame || whosays == GE_ICS) {
10187         /* OK -- not an ICS game, or ICS said it was done */
10188         StopClocks();
10189         if (!isIcsGame && !appData.noChessProgram)
10190           SetUserThinkingEnables();
10191
10192         /* [HGM] if a machine claims the game end we verify this claim */
10193         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10194             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10195                 char claimer;
10196                 ChessMove trueResult = (ChessMove) -1;
10197
10198                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10199                                             first.twoMachinesColor[0] :
10200                                             second.twoMachinesColor[0] ;
10201
10202                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10203                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10204                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10205                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10206                 } else
10207                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10208                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10209                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10210                 } else
10211                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10212                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10213                 }
10214
10215                 // now verify win claims, but not in drop games, as we don't understand those yet
10216                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10217                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10218                     (result == WhiteWins && claimer == 'w' ||
10219                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10220                       if (appData.debugMode) {
10221                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10222                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10223                       }
10224                       if(result != trueResult) {
10225                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10226                               result = claimer == 'w' ? BlackWins : WhiteWins;
10227                               resultDetails = buf;
10228                       }
10229                 } else
10230                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10231                     && (forwardMostMove <= backwardMostMove ||
10232                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10233                         (claimer=='b')==(forwardMostMove&1))
10234                                                                                   ) {
10235                       /* [HGM] verify: draws that were not flagged are false claims */
10236                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10237                       result = claimer == 'w' ? BlackWins : WhiteWins;
10238                       resultDetails = buf;
10239                 }
10240                 /* (Claiming a loss is accepted no questions asked!) */
10241             }
10242             /* [HGM] bare: don't allow bare King to win */
10243             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10244                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10245                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10246                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10247                && result != GameIsDrawn)
10248             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10249                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10250                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10251                         if(p >= 0 && p <= (int)WhiteKing) k++;
10252                 }
10253                 if (appData.debugMode) {
10254                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10255                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10256                 }
10257                 if(k <= 1) {
10258                         result = GameIsDrawn;
10259                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10260                         resultDetails = buf;
10261                 }
10262             }
10263         }
10264
10265
10266         if(serverMoves != NULL && !loadFlag) { char c = '=';
10267             if(result==WhiteWins) c = '+';
10268             if(result==BlackWins) c = '-';
10269             if(resultDetails != NULL)
10270                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10271         }
10272         if (resultDetails != NULL) {
10273             gameInfo.result = result;
10274             gameInfo.resultDetails = StrSave(resultDetails);
10275
10276             /* display last move only if game was not loaded from file */
10277             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10278                 DisplayMove(currentMove - 1);
10279
10280             if (forwardMostMove != 0) {
10281                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10282                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10283                                                                 ) {
10284                     if (*appData.saveGameFile != NULLCHAR) {
10285                         SaveGameToFile(appData.saveGameFile, TRUE);
10286                     } else if (appData.autoSaveGames) {
10287                         AutoSaveGame();
10288                     }
10289                     if (*appData.savePositionFile != NULLCHAR) {
10290                         SavePositionToFile(appData.savePositionFile);
10291                     }
10292                 }
10293             }
10294
10295             /* Tell program how game ended in case it is learning */
10296             /* [HGM] Moved this to after saving the PGN, just in case */
10297             /* engine died and we got here through time loss. In that */
10298             /* case we will get a fatal error writing the pipe, which */
10299             /* would otherwise lose us the PGN.                       */
10300             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10301             /* output during GameEnds should never be fatal anymore   */
10302             if (gameMode == MachinePlaysWhite ||
10303                 gameMode == MachinePlaysBlack ||
10304                 gameMode == TwoMachinesPlay ||
10305                 gameMode == IcsPlayingWhite ||
10306                 gameMode == IcsPlayingBlack ||
10307                 gameMode == BeginningOfGame) {
10308                 char buf[MSG_SIZ];
10309                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10310                         resultDetails);
10311                 if (first.pr != NoProc) {
10312                     SendToProgram(buf, &first);
10313                 }
10314                 if (second.pr != NoProc &&
10315                     gameMode == TwoMachinesPlay) {
10316                     SendToProgram(buf, &second);
10317                 }
10318             }
10319         }
10320
10321         if (appData.icsActive) {
10322             if (appData.quietPlay &&
10323                 (gameMode == IcsPlayingWhite ||
10324                  gameMode == IcsPlayingBlack)) {
10325                 SendToICS(ics_prefix);
10326                 SendToICS("set shout 1\n");
10327             }
10328             nextGameMode = IcsIdle;
10329             ics_user_moved = FALSE;
10330             /* clean up premove.  It's ugly when the game has ended and the
10331              * premove highlights are still on the board.
10332              */
10333             if (gotPremove) {
10334               gotPremove = FALSE;
10335               ClearPremoveHighlights();
10336               DrawPosition(FALSE, boards[currentMove]);
10337             }
10338             if (whosays == GE_ICS) {
10339                 switch (result) {
10340                 case WhiteWins:
10341                     if (gameMode == IcsPlayingWhite)
10342                         PlayIcsWinSound();
10343                     else if(gameMode == IcsPlayingBlack)
10344                         PlayIcsLossSound();
10345                     break;
10346                 case BlackWins:
10347                     if (gameMode == IcsPlayingBlack)
10348                         PlayIcsWinSound();
10349                     else if(gameMode == IcsPlayingWhite)
10350                         PlayIcsLossSound();
10351                     break;
10352                 case GameIsDrawn:
10353                     PlayIcsDrawSound();
10354                     break;
10355                 default:
10356                     PlayIcsUnfinishedSound();
10357                 }
10358             }
10359         } else if (gameMode == EditGame ||
10360                    gameMode == PlayFromGameFile ||
10361                    gameMode == AnalyzeMode ||
10362                    gameMode == AnalyzeFile) {
10363             nextGameMode = gameMode;
10364         } else {
10365             nextGameMode = EndOfGame;
10366         }
10367         pausing = FALSE;
10368         ModeHighlight();
10369     } else {
10370         nextGameMode = gameMode;
10371     }
10372
10373     if (appData.noChessProgram) {
10374         gameMode = nextGameMode;
10375         ModeHighlight();
10376         endingGame = 0; /* [HGM] crash */
10377         return;
10378     }
10379
10380     if (first.reuse) {
10381         /* Put first chess program into idle state */
10382         if (first.pr != NoProc &&
10383             (gameMode == MachinePlaysWhite ||
10384              gameMode == MachinePlaysBlack ||
10385              gameMode == TwoMachinesPlay ||
10386              gameMode == IcsPlayingWhite ||
10387              gameMode == IcsPlayingBlack ||
10388              gameMode == BeginningOfGame)) {
10389             SendToProgram("force\n", &first);
10390             if (first.usePing) {
10391               char buf[MSG_SIZ];
10392               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10393               SendToProgram(buf, &first);
10394             }
10395         }
10396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10397         /* Kill off first chess program */
10398         if (first.isr != NULL)
10399           RemoveInputSource(first.isr);
10400         first.isr = NULL;
10401
10402         if (first.pr != NoProc) {
10403             ExitAnalyzeMode();
10404             DoSleep( appData.delayBeforeQuit );
10405             SendToProgram("quit\n", &first);
10406             DoSleep( appData.delayAfterQuit );
10407             DestroyChildProcess(first.pr, first.useSigterm);
10408         }
10409         first.pr = NoProc;
10410     }
10411     if (second.reuse) {
10412         /* Put second chess program into idle state */
10413         if (second.pr != NoProc &&
10414             gameMode == TwoMachinesPlay) {
10415             SendToProgram("force\n", &second);
10416             if (second.usePing) {
10417               char buf[MSG_SIZ];
10418               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10419               SendToProgram(buf, &second);
10420             }
10421         }
10422     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10423         /* Kill off second chess program */
10424         if (second.isr != NULL)
10425           RemoveInputSource(second.isr);
10426         second.isr = NULL;
10427
10428         if (second.pr != NoProc) {
10429             DoSleep( appData.delayBeforeQuit );
10430             SendToProgram("quit\n", &second);
10431             DoSleep( appData.delayAfterQuit );
10432             DestroyChildProcess(second.pr, second.useSigterm);
10433         }
10434         second.pr = NoProc;
10435     }
10436
10437     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10438         char resChar = '=';
10439         switch (result) {
10440         case WhiteWins:
10441           resChar = '+';
10442           if (first.twoMachinesColor[0] == 'w') {
10443             first.matchWins++;
10444           } else {
10445             second.matchWins++;
10446           }
10447           break;
10448         case BlackWins:
10449           resChar = '-';
10450           if (first.twoMachinesColor[0] == 'b') {
10451             first.matchWins++;
10452           } else {
10453             second.matchWins++;
10454           }
10455           break;
10456         case GameUnfinished:
10457           resChar = ' ';
10458         default:
10459           break;
10460         }
10461
10462         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10463         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10464             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10465             ReserveGame(nextGame, resChar); // sets nextGame
10466             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10467             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10468         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10469
10470         if (nextGame <= appData.matchGames && !abortMatch) {
10471             gameMode = nextGameMode;
10472             matchGame = nextGame; // this will be overruled in tourney mode!
10473             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10474             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10475             endingGame = 0; /* [HGM] crash */
10476             return;
10477         } else {
10478             gameMode = nextGameMode;
10479             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10480                      first.tidy, second.tidy,
10481                      first.matchWins, second.matchWins,
10482                      appData.matchGames - (first.matchWins + second.matchWins));
10483             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10484             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10485             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10486                 first.twoMachinesColor = "black\n";
10487                 second.twoMachinesColor = "white\n";
10488             } else {
10489                 first.twoMachinesColor = "white\n";
10490                 second.twoMachinesColor = "black\n";
10491             }
10492         }
10493     }
10494     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10495         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10496       ExitAnalyzeMode();
10497     gameMode = nextGameMode;
10498     ModeHighlight();
10499     endingGame = 0;  /* [HGM] crash */
10500     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10501         if(matchMode == TRUE) { // match through command line: exit with or without popup
10502             if(ranking) {
10503                 ToNrEvent(forwardMostMove);
10504                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10505                 else ExitEvent(0);
10506             } else DisplayFatalError(buf, 0, 0);
10507         } else { // match through menu; just stop, with or without popup
10508             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10509             ModeHighlight();
10510             if(ranking){
10511                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10512             } else DisplayNote(buf);
10513       }
10514       if(ranking) free(ranking);
10515     }
10516 }
10517
10518 /* Assumes program was just initialized (initString sent).
10519    Leaves program in force mode. */
10520 void
10521 FeedMovesToProgram(cps, upto)
10522      ChessProgramState *cps;
10523      int upto;
10524 {
10525     int i;
10526
10527     if (appData.debugMode)
10528       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10529               startedFromSetupPosition ? "position and " : "",
10530               backwardMostMove, upto, cps->which);
10531     if(currentlyInitializedVariant != gameInfo.variant) {
10532       char buf[MSG_SIZ];
10533         // [HGM] variantswitch: make engine aware of new variant
10534         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10535                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10536         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10537         SendToProgram(buf, cps);
10538         currentlyInitializedVariant = gameInfo.variant;
10539     }
10540     SendToProgram("force\n", cps);
10541     if (startedFromSetupPosition) {
10542         SendBoard(cps, backwardMostMove);
10543     if (appData.debugMode) {
10544         fprintf(debugFP, "feedMoves\n");
10545     }
10546     }
10547     for (i = backwardMostMove; i < upto; i++) {
10548         SendMoveToProgram(i, cps);
10549     }
10550 }
10551
10552
10553 int
10554 ResurrectChessProgram()
10555 {
10556      /* The chess program may have exited.
10557         If so, restart it and feed it all the moves made so far. */
10558     static int doInit = 0;
10559
10560     if (appData.noChessProgram) return 1;
10561
10562     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10563         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10564         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10565         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10566     } else {
10567         if (first.pr != NoProc) return 1;
10568         StartChessProgram(&first);
10569     }
10570     InitChessProgram(&first, FALSE);
10571     FeedMovesToProgram(&first, currentMove);
10572
10573     if (!first.sendTime) {
10574         /* can't tell gnuchess what its clock should read,
10575            so we bow to its notion. */
10576         ResetClocks();
10577         timeRemaining[0][currentMove] = whiteTimeRemaining;
10578         timeRemaining[1][currentMove] = blackTimeRemaining;
10579     }
10580
10581     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10582                 appData.icsEngineAnalyze) && first.analysisSupport) {
10583       SendToProgram("analyze\n", &first);
10584       first.analyzing = TRUE;
10585     }
10586     return 1;
10587 }
10588
10589 /*
10590  * Button procedures
10591  */
10592 void
10593 Reset(redraw, init)
10594      int redraw, init;
10595 {
10596     int i;
10597
10598     if (appData.debugMode) {
10599         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10600                 redraw, init, gameMode);
10601     }
10602     CleanupTail(); // [HGM] vari: delete any stored variations
10603     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10604     pausing = pauseExamInvalid = FALSE;
10605     startedFromSetupPosition = blackPlaysFirst = FALSE;
10606     firstMove = TRUE;
10607     whiteFlag = blackFlag = FALSE;
10608     userOfferedDraw = FALSE;
10609     hintRequested = bookRequested = FALSE;
10610     first.maybeThinking = FALSE;
10611     second.maybeThinking = FALSE;
10612     first.bookSuspend = FALSE; // [HGM] book
10613     second.bookSuspend = FALSE;
10614     thinkOutput[0] = NULLCHAR;
10615     lastHint[0] = NULLCHAR;
10616     ClearGameInfo(&gameInfo);
10617     gameInfo.variant = StringToVariant(appData.variant);
10618     ics_user_moved = ics_clock_paused = FALSE;
10619     ics_getting_history = H_FALSE;
10620     ics_gamenum = -1;
10621     white_holding[0] = black_holding[0] = NULLCHAR;
10622     ClearProgramStats();
10623     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10624
10625     ResetFrontEnd();
10626     ClearHighlights();
10627     flipView = appData.flipView;
10628     ClearPremoveHighlights();
10629     gotPremove = FALSE;
10630     alarmSounded = FALSE;
10631
10632     GameEnds(EndOfFile, NULL, GE_PLAYER);
10633     if(appData.serverMovesName != NULL) {
10634         /* [HGM] prepare to make moves file for broadcasting */
10635         clock_t t = clock();
10636         if(serverMoves != NULL) fclose(serverMoves);
10637         serverMoves = fopen(appData.serverMovesName, "r");
10638         if(serverMoves != NULL) {
10639             fclose(serverMoves);
10640             /* delay 15 sec before overwriting, so all clients can see end */
10641             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10642         }
10643         serverMoves = fopen(appData.serverMovesName, "w");
10644     }
10645
10646     ExitAnalyzeMode();
10647     gameMode = BeginningOfGame;
10648     ModeHighlight();
10649     if(appData.icsActive) gameInfo.variant = VariantNormal;
10650     currentMove = forwardMostMove = backwardMostMove = 0;
10651     InitPosition(redraw);
10652     for (i = 0; i < MAX_MOVES; i++) {
10653         if (commentList[i] != NULL) {
10654             free(commentList[i]);
10655             commentList[i] = NULL;
10656         }
10657     }
10658     ResetClocks();
10659     timeRemaining[0][0] = whiteTimeRemaining;
10660     timeRemaining[1][0] = blackTimeRemaining;
10661
10662     if (first.pr == NoProc) {
10663         StartChessProgram(&first);
10664     }
10665     if (init) {
10666             InitChessProgram(&first, startedFromSetupPosition);
10667     }
10668     DisplayTitle("");
10669     DisplayMessage("", "");
10670     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10671     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10672 }
10673
10674 void
10675 AutoPlayGameLoop()
10676 {
10677     for (;;) {
10678         if (!AutoPlayOneMove())
10679           return;
10680         if (matchMode || appData.timeDelay == 0)
10681           continue;
10682         if (appData.timeDelay < 0)
10683           return;
10684         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10685         break;
10686     }
10687 }
10688
10689
10690 int
10691 AutoPlayOneMove()
10692 {
10693     int fromX, fromY, toX, toY;
10694
10695     if (appData.debugMode) {
10696       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10697     }
10698
10699     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10700       return FALSE;
10701
10702     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10703       pvInfoList[currentMove].depth = programStats.depth;
10704       pvInfoList[currentMove].score = programStats.score;
10705       pvInfoList[currentMove].time  = 0;
10706       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10707     }
10708
10709     if (currentMove >= forwardMostMove) {
10710       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10711 //      gameMode = EndOfGame;
10712 //      ModeHighlight();
10713
10714       /* [AS] Clear current move marker at the end of a game */
10715       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10716
10717       return FALSE;
10718     }
10719
10720     toX = moveList[currentMove][2] - AAA;
10721     toY = moveList[currentMove][3] - ONE;
10722
10723     if (moveList[currentMove][1] == '@') {
10724         if (appData.highlightLastMove) {
10725             SetHighlights(-1, -1, toX, toY);
10726         }
10727     } else {
10728         fromX = moveList[currentMove][0] - AAA;
10729         fromY = moveList[currentMove][1] - ONE;
10730
10731         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10732
10733         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10734
10735         if (appData.highlightLastMove) {
10736             SetHighlights(fromX, fromY, toX, toY);
10737         }
10738     }
10739     DisplayMove(currentMove);
10740     SendMoveToProgram(currentMove++, &first);
10741     DisplayBothClocks();
10742     DrawPosition(FALSE, boards[currentMove]);
10743     // [HGM] PV info: always display, routine tests if empty
10744     DisplayComment(currentMove - 1, commentList[currentMove]);
10745     return TRUE;
10746 }
10747
10748
10749 int
10750 LoadGameOneMove(readAhead)
10751      ChessMove readAhead;
10752 {
10753     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10754     char promoChar = NULLCHAR;
10755     ChessMove moveType;
10756     char move[MSG_SIZ];
10757     char *p, *q;
10758
10759     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10760         gameMode != AnalyzeMode && gameMode != Training) {
10761         gameFileFP = NULL;
10762         return FALSE;
10763     }
10764
10765     yyboardindex = forwardMostMove;
10766     if (readAhead != EndOfFile) {
10767       moveType = readAhead;
10768     } else {
10769       if (gameFileFP == NULL)
10770           return FALSE;
10771       moveType = (ChessMove) Myylex();
10772     }
10773
10774     done = FALSE;
10775     switch (moveType) {
10776       case Comment:
10777         if (appData.debugMode)
10778           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10779         p = yy_text;
10780
10781         /* append the comment but don't display it */
10782         AppendComment(currentMove, p, FALSE);
10783         return TRUE;
10784
10785       case WhiteCapturesEnPassant:
10786       case BlackCapturesEnPassant:
10787       case WhitePromotion:
10788       case BlackPromotion:
10789       case WhiteNonPromotion:
10790       case BlackNonPromotion:
10791       case NormalMove:
10792       case WhiteKingSideCastle:
10793       case WhiteQueenSideCastle:
10794       case BlackKingSideCastle:
10795       case BlackQueenSideCastle:
10796       case WhiteKingSideCastleWild:
10797       case WhiteQueenSideCastleWild:
10798       case BlackKingSideCastleWild:
10799       case BlackQueenSideCastleWild:
10800       /* PUSH Fabien */
10801       case WhiteHSideCastleFR:
10802       case WhiteASideCastleFR:
10803       case BlackHSideCastleFR:
10804       case BlackASideCastleFR:
10805       /* POP Fabien */
10806         if (appData.debugMode)
10807           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10808         fromX = currentMoveString[0] - AAA;
10809         fromY = currentMoveString[1] - ONE;
10810         toX = currentMoveString[2] - AAA;
10811         toY = currentMoveString[3] - ONE;
10812         promoChar = currentMoveString[4];
10813         break;
10814
10815       case WhiteDrop:
10816       case BlackDrop:
10817         if (appData.debugMode)
10818           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10819         fromX = moveType == WhiteDrop ?
10820           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10821         (int) CharToPiece(ToLower(currentMoveString[0]));
10822         fromY = DROP_RANK;
10823         toX = currentMoveString[2] - AAA;
10824         toY = currentMoveString[3] - ONE;
10825         break;
10826
10827       case WhiteWins:
10828       case BlackWins:
10829       case GameIsDrawn:
10830       case GameUnfinished:
10831         if (appData.debugMode)
10832           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10833         p = strchr(yy_text, '{');
10834         if (p == NULL) p = strchr(yy_text, '(');
10835         if (p == NULL) {
10836             p = yy_text;
10837             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10838         } else {
10839             q = strchr(p, *p == '{' ? '}' : ')');
10840             if (q != NULL) *q = NULLCHAR;
10841             p++;
10842         }
10843         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10844         GameEnds(moveType, p, GE_FILE);
10845         done = TRUE;
10846         if (cmailMsgLoaded) {
10847             ClearHighlights();
10848             flipView = WhiteOnMove(currentMove);
10849             if (moveType == GameUnfinished) flipView = !flipView;
10850             if (appData.debugMode)
10851               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10852         }
10853         break;
10854
10855       case EndOfFile:
10856         if (appData.debugMode)
10857           fprintf(debugFP, "Parser hit end of file\n");
10858         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10859           case MT_NONE:
10860           case MT_CHECK:
10861             break;
10862           case MT_CHECKMATE:
10863           case MT_STAINMATE:
10864             if (WhiteOnMove(currentMove)) {
10865                 GameEnds(BlackWins, "Black mates", GE_FILE);
10866             } else {
10867                 GameEnds(WhiteWins, "White mates", GE_FILE);
10868             }
10869             break;
10870           case MT_STALEMATE:
10871             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10872             break;
10873         }
10874         done = TRUE;
10875         break;
10876
10877       case MoveNumberOne:
10878         if (lastLoadGameStart == GNUChessGame) {
10879             /* GNUChessGames have numbers, but they aren't move numbers */
10880             if (appData.debugMode)
10881               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10882                       yy_text, (int) moveType);
10883             return LoadGameOneMove(EndOfFile); /* tail recursion */
10884         }
10885         /* else fall thru */
10886
10887       case XBoardGame:
10888       case GNUChessGame:
10889       case PGNTag:
10890         /* Reached start of next game in file */
10891         if (appData.debugMode)
10892           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10893         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10894           case MT_NONE:
10895           case MT_CHECK:
10896             break;
10897           case MT_CHECKMATE:
10898           case MT_STAINMATE:
10899             if (WhiteOnMove(currentMove)) {
10900                 GameEnds(BlackWins, "Black mates", GE_FILE);
10901             } else {
10902                 GameEnds(WhiteWins, "White mates", GE_FILE);
10903             }
10904             break;
10905           case MT_STALEMATE:
10906             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10907             break;
10908         }
10909         done = TRUE;
10910         break;
10911
10912       case PositionDiagram:     /* should not happen; ignore */
10913       case ElapsedTime:         /* ignore */
10914       case NAG:                 /* ignore */
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10917                   yy_text, (int) moveType);
10918         return LoadGameOneMove(EndOfFile); /* tail recursion */
10919
10920       case IllegalMove:
10921         if (appData.testLegality) {
10922             if (appData.debugMode)
10923               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10924             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10925                     (forwardMostMove / 2) + 1,
10926                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10927             DisplayError(move, 0);
10928             done = TRUE;
10929         } else {
10930             if (appData.debugMode)
10931               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10932                       yy_text, currentMoveString);
10933             fromX = currentMoveString[0] - AAA;
10934             fromY = currentMoveString[1] - ONE;
10935             toX = currentMoveString[2] - AAA;
10936             toY = currentMoveString[3] - ONE;
10937             promoChar = currentMoveString[4];
10938         }
10939         break;
10940
10941       case AmbiguousMove:
10942         if (appData.debugMode)
10943           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10944         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10945                 (forwardMostMove / 2) + 1,
10946                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10947         DisplayError(move, 0);
10948         done = TRUE;
10949         break;
10950
10951       default:
10952       case ImpossibleMove:
10953         if (appData.debugMode)
10954           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10955         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10956                 (forwardMostMove / 2) + 1,
10957                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10958         DisplayError(move, 0);
10959         done = TRUE;
10960         break;
10961     }
10962
10963     if (done) {
10964         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10965             DrawPosition(FALSE, boards[currentMove]);
10966             DisplayBothClocks();
10967             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10968               DisplayComment(currentMove - 1, commentList[currentMove]);
10969         }
10970         (void) StopLoadGameTimer();
10971         gameFileFP = NULL;
10972         cmailOldMove = forwardMostMove;
10973         return FALSE;
10974     } else {
10975         /* currentMoveString is set as a side-effect of yylex */
10976
10977         thinkOutput[0] = NULLCHAR;
10978         MakeMove(fromX, fromY, toX, toY, promoChar);
10979         currentMove = forwardMostMove;
10980         return TRUE;
10981     }
10982 }
10983
10984 /* Load the nth game from the given file */
10985 int
10986 LoadGameFromFile(filename, n, title, useList)
10987      char *filename;
10988      int n;
10989      char *title;
10990      /*Boolean*/ int useList;
10991 {
10992     FILE *f;
10993     char buf[MSG_SIZ];
10994
10995     if (strcmp(filename, "-") == 0) {
10996         f = stdin;
10997         title = "stdin";
10998     } else {
10999         f = fopen(filename, "rb");
11000         if (f == NULL) {
11001           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11002             DisplayError(buf, errno);
11003             return FALSE;
11004         }
11005     }
11006     if (fseek(f, 0, 0) == -1) {
11007         /* f is not seekable; probably a pipe */
11008         useList = FALSE;
11009     }
11010     if (useList && n == 0) {
11011         int error = GameListBuild(f);
11012         if (error) {
11013             DisplayError(_("Cannot build game list"), error);
11014         } else if (!ListEmpty(&gameList) &&
11015                    ((ListGame *) gameList.tailPred)->number > 1) {
11016             GameListPopUp(f, title);
11017             return TRUE;
11018         }
11019         GameListDestroy();
11020         n = 1;
11021     }
11022     if (n == 0) n = 1;
11023     return LoadGame(f, n, title, FALSE);
11024 }
11025
11026
11027 void
11028 MakeRegisteredMove()
11029 {
11030     int fromX, fromY, toX, toY;
11031     char promoChar;
11032     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11033         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11034           case CMAIL_MOVE:
11035           case CMAIL_DRAW:
11036             if (appData.debugMode)
11037               fprintf(debugFP, "Restoring %s for game %d\n",
11038                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11039
11040             thinkOutput[0] = NULLCHAR;
11041             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11042             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11043             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11044             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11045             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11046             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11047             MakeMove(fromX, fromY, toX, toY, promoChar);
11048             ShowMove(fromX, fromY, toX, toY);
11049
11050             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11051               case MT_NONE:
11052               case MT_CHECK:
11053                 break;
11054
11055               case MT_CHECKMATE:
11056               case MT_STAINMATE:
11057                 if (WhiteOnMove(currentMove)) {
11058                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11059                 } else {
11060                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11061                 }
11062                 break;
11063
11064               case MT_STALEMATE:
11065                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11066                 break;
11067             }
11068
11069             break;
11070
11071           case CMAIL_RESIGN:
11072             if (WhiteOnMove(currentMove)) {
11073                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11074             } else {
11075                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11076             }
11077             break;
11078
11079           case CMAIL_ACCEPT:
11080             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11081             break;
11082
11083           default:
11084             break;
11085         }
11086     }
11087
11088     return;
11089 }
11090
11091 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11092 int
11093 CmailLoadGame(f, gameNumber, title, useList)
11094      FILE *f;
11095      int gameNumber;
11096      char *title;
11097      int useList;
11098 {
11099     int retVal;
11100
11101     if (gameNumber > nCmailGames) {
11102         DisplayError(_("No more games in this message"), 0);
11103         return FALSE;
11104     }
11105     if (f == lastLoadGameFP) {
11106         int offset = gameNumber - lastLoadGameNumber;
11107         if (offset == 0) {
11108             cmailMsg[0] = NULLCHAR;
11109             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11110                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11111                 nCmailMovesRegistered--;
11112             }
11113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11114             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11115                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11116             }
11117         } else {
11118             if (! RegisterMove()) return FALSE;
11119         }
11120     }
11121
11122     retVal = LoadGame(f, gameNumber, title, useList);
11123
11124     /* Make move registered during previous look at this game, if any */
11125     MakeRegisteredMove();
11126
11127     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11128         commentList[currentMove]
11129           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11130         DisplayComment(currentMove - 1, commentList[currentMove]);
11131     }
11132
11133     return retVal;
11134 }
11135
11136 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11137 int
11138 ReloadGame(offset)
11139      int offset;
11140 {
11141     int gameNumber = lastLoadGameNumber + offset;
11142     if (lastLoadGameFP == NULL) {
11143         DisplayError(_("No game has been loaded yet"), 0);
11144         return FALSE;
11145     }
11146     if (gameNumber <= 0) {
11147         DisplayError(_("Can't back up any further"), 0);
11148         return FALSE;
11149     }
11150     if (cmailMsgLoaded) {
11151         return CmailLoadGame(lastLoadGameFP, gameNumber,
11152                              lastLoadGameTitle, lastLoadGameUseList);
11153     } else {
11154         return LoadGame(lastLoadGameFP, gameNumber,
11155                         lastLoadGameTitle, lastLoadGameUseList);
11156     }
11157 }
11158
11159 int keys[EmptySquare+1];
11160
11161 int
11162 PositionMatches(Board b1, Board b2)
11163 {
11164     int r, f, sum=0;
11165     switch(appData.searchMode) {
11166         case 1: return CompareWithRights(b1, b2);
11167         case 2:
11168             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11169                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11170             }
11171             return TRUE;
11172         case 3:
11173             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11174               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11175                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11176             }
11177             return sum==0;
11178         case 4:
11179             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11180                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11181             }
11182             return sum==0;
11183     }
11184     return TRUE;
11185 }
11186
11187 #define Q_PROMO  4
11188 #define Q_EP     3
11189 #define Q_BCASTL 2
11190 #define Q_WCASTL 1
11191
11192 int pieceList[256], quickBoard[256];
11193 ChessSquare pieceType[256] = { EmptySquare };
11194 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11195 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11196 int soughtTotal, turn;
11197 Boolean epOK, flipSearch;
11198
11199 typedef struct {
11200     unsigned char piece, to;
11201 } Move;
11202
11203 #define DSIZE (250000)
11204
11205 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11206 Move *moveDatabase = initialSpace;
11207 unsigned int movePtr, dataSize = DSIZE;
11208
11209 int MakePieceList(Board board, int *counts)
11210 {
11211     int r, f, n=Q_PROMO, total=0;
11212     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11213     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11214         int sq = f + (r<<4);
11215         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11216             quickBoard[sq] = ++n;
11217             pieceList[n] = sq;
11218             pieceType[n] = board[r][f];
11219             counts[board[r][f]]++;
11220             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11221             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11222             total++;
11223         }
11224     }
11225     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11226     return total;
11227 }
11228
11229 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11230 {
11231     int sq = fromX + (fromY<<4);
11232     int piece = quickBoard[sq];
11233     quickBoard[sq] = 0;
11234     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11235     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11236         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11237         moveDatabase[movePtr++].piece = Q_WCASTL;
11238         quickBoard[sq] = piece;
11239         piece = quickBoard[from]; quickBoard[from] = 0;
11240         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11241     } else
11242     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11243         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11244         moveDatabase[movePtr++].piece = Q_BCASTL;
11245         quickBoard[sq] = piece;
11246         piece = quickBoard[from]; quickBoard[from] = 0;
11247         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11248     } else
11249     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11250         quickBoard[(fromY<<4)+toX] = 0;
11251         moveDatabase[movePtr].piece = Q_EP;
11252         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11253         moveDatabase[movePtr].to = sq;
11254     } else
11255     if(promoPiece != pieceType[piece]) {
11256         moveDatabase[movePtr++].piece = Q_PROMO;
11257         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11258     }
11259     moveDatabase[movePtr].piece = piece;
11260     quickBoard[sq] = piece;
11261     movePtr++;
11262 }
11263
11264 int PackGame(Board board)
11265 {
11266     Move *newSpace = NULL;
11267     moveDatabase[movePtr].piece = 0; // terminate previous game
11268     if(movePtr > dataSize) {
11269         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11270         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11271         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11272         if(newSpace) {
11273             int i;
11274             Move *p = moveDatabase, *q = newSpace;
11275             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11276             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11277             moveDatabase = newSpace;
11278         } else { // calloc failed, we must be out of memory. Too bad...
11279             dataSize = 0; // prevent calloc events for all subsequent games
11280             return 0;     // and signal this one isn't cached
11281         }
11282     }
11283     movePtr++;
11284     MakePieceList(board, counts);
11285     return movePtr;
11286 }
11287
11288 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11289 {   // compare according to search mode
11290     int r, f;
11291     switch(appData.searchMode)
11292     {
11293       case 1: // exact position match
11294         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11296             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11297         }
11298         break;
11299       case 2: // can have extra material on empty squares
11300         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11301             if(board[r][f] == EmptySquare) continue;
11302             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11303         }
11304         break;
11305       case 3: // material with exact Pawn structure
11306         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11307             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11308             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11309         } // fall through to material comparison
11310       case 4: // exact material
11311         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11312         break;
11313       case 6: // material range with given imbalance
11314         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11315         // fall through to range comparison
11316       case 5: // material range
11317         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11318     }
11319     return TRUE;
11320 }
11321
11322 int QuickScan(Board board, Move *move)
11323 {   // reconstruct game,and compare all positions in it
11324     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11325     do {
11326         int piece = move->piece;
11327         int to = move->to, from = pieceList[piece];
11328         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11329           if(!piece) return -1;
11330           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11331             piece = (++move)->piece;
11332             from = pieceList[piece];
11333             counts[pieceType[piece]]--;
11334             pieceType[piece] = (ChessSquare) move->to;
11335             counts[move->to]++;
11336           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11337             counts[pieceType[quickBoard[to]]]--;
11338             quickBoard[to] = 0; total--;
11339             move++;
11340             continue;
11341           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11342             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11343             from  = pieceList[piece]; // so this must be King
11344             quickBoard[from] = 0;
11345             quickBoard[to] = piece;
11346             pieceList[piece] = to;
11347             move++;
11348             continue;
11349           }
11350         }
11351         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11352         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11353         quickBoard[from] = 0;
11354         quickBoard[to] = piece;
11355         pieceList[piece] = to;
11356         cnt++; turn ^= 3;
11357         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11358            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11359            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11360                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11361           ) {
11362             static int lastCounts[EmptySquare+1];
11363             int i;
11364             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11365             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11366         } else stretch = 0;
11367         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11368         move++;
11369     } while(1);
11370 }
11371
11372 void InitSearch()
11373 {
11374     int r, f;
11375     flipSearch = FALSE;
11376     CopyBoard(soughtBoard, boards[currentMove]);
11377     soughtTotal = MakePieceList(soughtBoard, maxSought);
11378     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11379     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11380     CopyBoard(reverseBoard, boards[currentMove]);
11381     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11382         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11383         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11384         reverseBoard[r][f] = piece;
11385     }
11386     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11387     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11388     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11389                  || (boards[currentMove][CASTLING][2] == NoRights || 
11390                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11391                  && (boards[currentMove][CASTLING][5] == NoRights || 
11392                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11393       ) {
11394         flipSearch = TRUE;
11395         CopyBoard(flipBoard, soughtBoard);
11396         CopyBoard(rotateBoard, reverseBoard);
11397         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11398             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11399             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11400         }
11401     }
11402     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11403     if(appData.searchMode >= 5) {
11404         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11405         MakePieceList(soughtBoard, minSought);
11406         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11407     }
11408     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11409         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11410 }
11411
11412 GameInfo dummyInfo;
11413
11414 int GameContainsPosition(FILE *f, ListGame *lg)
11415 {
11416     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11417     int fromX, fromY, toX, toY;
11418     char promoChar;
11419     static int initDone=FALSE;
11420
11421     // weed out games based on numerical tag comparison
11422     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11423     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11424     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11425     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11426     if(!initDone) {
11427         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11428         initDone = TRUE;
11429     }
11430     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11431     else CopyBoard(boards[scratch], initialPosition); // default start position
11432     if(lg->moves) {
11433         turn = btm + 1;
11434         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11435         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11436     }
11437     if(btm) plyNr++;
11438     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11439     fseek(f, lg->offset, 0);
11440     yynewfile(f);
11441     while(1) {
11442         yyboardindex = scratch;
11443         quickFlag = plyNr+1;
11444         next = Myylex();
11445         quickFlag = 0;
11446         switch(next) {
11447             case PGNTag:
11448                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11449             default:
11450                 continue;
11451
11452             case XBoardGame:
11453             case GNUChessGame:
11454                 if(plyNr) return -1; // after we have seen moves, this is for new game
11455               continue;
11456
11457             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11458             case ImpossibleMove:
11459             case WhiteWins: // game ends here with these four
11460             case BlackWins:
11461             case GameIsDrawn:
11462             case GameUnfinished:
11463                 return -1;
11464
11465             case IllegalMove:
11466                 if(appData.testLegality) return -1;
11467             case WhiteCapturesEnPassant:
11468             case BlackCapturesEnPassant:
11469             case WhitePromotion:
11470             case BlackPromotion:
11471             case WhiteNonPromotion:
11472             case BlackNonPromotion:
11473             case NormalMove:
11474             case WhiteKingSideCastle:
11475             case WhiteQueenSideCastle:
11476             case BlackKingSideCastle:
11477             case BlackQueenSideCastle:
11478             case WhiteKingSideCastleWild:
11479             case WhiteQueenSideCastleWild:
11480             case BlackKingSideCastleWild:
11481             case BlackQueenSideCastleWild:
11482             case WhiteHSideCastleFR:
11483             case WhiteASideCastleFR:
11484             case BlackHSideCastleFR:
11485             case BlackASideCastleFR:
11486                 fromX = currentMoveString[0] - AAA;
11487                 fromY = currentMoveString[1] - ONE;
11488                 toX = currentMoveString[2] - AAA;
11489                 toY = currentMoveString[3] - ONE;
11490                 promoChar = currentMoveString[4];
11491                 break;
11492             case WhiteDrop:
11493             case BlackDrop:
11494                 fromX = next == WhiteDrop ?
11495                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11496                   (int) CharToPiece(ToLower(currentMoveString[0]));
11497                 fromY = DROP_RANK;
11498                 toX = currentMoveString[2] - AAA;
11499                 toY = currentMoveString[3] - ONE;
11500                 promoChar = 0;
11501                 break;
11502         }
11503         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11504         plyNr++;
11505         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11506         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11507         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11508         if(appData.findMirror) {
11509             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11510             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11511         }
11512     }
11513 }
11514
11515 /* Load the nth game from open file f */
11516 int
11517 LoadGame(f, gameNumber, title, useList)
11518      FILE *f;
11519      int gameNumber;
11520      char *title;
11521      int useList;
11522 {
11523     ChessMove cm;
11524     char buf[MSG_SIZ];
11525     int gn = gameNumber;
11526     ListGame *lg = NULL;
11527     int numPGNTags = 0;
11528     int err, pos = -1;
11529     GameMode oldGameMode;
11530     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11531
11532     if (appData.debugMode)
11533         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11534
11535     if (gameMode == Training )
11536         SetTrainingModeOff();
11537
11538     oldGameMode = gameMode;
11539     if (gameMode != BeginningOfGame) {
11540       Reset(FALSE, TRUE);
11541     }
11542
11543     gameFileFP = f;
11544     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11545         fclose(lastLoadGameFP);
11546     }
11547
11548     if (useList) {
11549         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11550
11551         if (lg) {
11552             fseek(f, lg->offset, 0);
11553             GameListHighlight(gameNumber);
11554             pos = lg->position;
11555             gn = 1;
11556         }
11557         else {
11558             DisplayError(_("Game number out of range"), 0);
11559             return FALSE;
11560         }
11561     } else {
11562         GameListDestroy();
11563         if (fseek(f, 0, 0) == -1) {
11564             if (f == lastLoadGameFP ?
11565                 gameNumber == lastLoadGameNumber + 1 :
11566                 gameNumber == 1) {
11567                 gn = 1;
11568             } else {
11569                 DisplayError(_("Can't seek on game file"), 0);
11570                 return FALSE;
11571             }
11572         }
11573     }
11574     lastLoadGameFP = f;
11575     lastLoadGameNumber = gameNumber;
11576     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11577     lastLoadGameUseList = useList;
11578
11579     yynewfile(f);
11580
11581     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11582       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11583                 lg->gameInfo.black);
11584             DisplayTitle(buf);
11585     } else if (*title != NULLCHAR) {
11586         if (gameNumber > 1) {
11587           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11588             DisplayTitle(buf);
11589         } else {
11590             DisplayTitle(title);
11591         }
11592     }
11593
11594     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11595         gameMode = PlayFromGameFile;
11596         ModeHighlight();
11597     }
11598
11599     currentMove = forwardMostMove = backwardMostMove = 0;
11600     CopyBoard(boards[0], initialPosition);
11601     StopClocks();
11602
11603     /*
11604      * Skip the first gn-1 games in the file.
11605      * Also skip over anything that precedes an identifiable
11606      * start of game marker, to avoid being confused by
11607      * garbage at the start of the file.  Currently
11608      * recognized start of game markers are the move number "1",
11609      * the pattern "gnuchess .* game", the pattern
11610      * "^[#;%] [^ ]* game file", and a PGN tag block.
11611      * A game that starts with one of the latter two patterns
11612      * will also have a move number 1, possibly
11613      * following a position diagram.
11614      * 5-4-02: Let's try being more lenient and allowing a game to
11615      * start with an unnumbered move.  Does that break anything?
11616      */
11617     cm = lastLoadGameStart = EndOfFile;
11618     while (gn > 0) {
11619         yyboardindex = forwardMostMove;
11620         cm = (ChessMove) Myylex();
11621         switch (cm) {
11622           case EndOfFile:
11623             if (cmailMsgLoaded) {
11624                 nCmailGames = CMAIL_MAX_GAMES - gn;
11625             } else {
11626                 Reset(TRUE, TRUE);
11627                 DisplayError(_("Game not found in file"), 0);
11628             }
11629             return FALSE;
11630
11631           case GNUChessGame:
11632           case XBoardGame:
11633             gn--;
11634             lastLoadGameStart = cm;
11635             break;
11636
11637           case MoveNumberOne:
11638             switch (lastLoadGameStart) {
11639               case GNUChessGame:
11640               case XBoardGame:
11641               case PGNTag:
11642                 break;
11643               case MoveNumberOne:
11644               case EndOfFile:
11645                 gn--;           /* count this game */
11646                 lastLoadGameStart = cm;
11647                 break;
11648               default:
11649                 /* impossible */
11650                 break;
11651             }
11652             break;
11653
11654           case PGNTag:
11655             switch (lastLoadGameStart) {
11656               case GNUChessGame:
11657               case PGNTag:
11658               case MoveNumberOne:
11659               case EndOfFile:
11660                 gn--;           /* count this game */
11661                 lastLoadGameStart = cm;
11662                 break;
11663               case XBoardGame:
11664                 lastLoadGameStart = cm; /* game counted already */
11665                 break;
11666               default:
11667                 /* impossible */
11668                 break;
11669             }
11670             if (gn > 0) {
11671                 do {
11672                     yyboardindex = forwardMostMove;
11673                     cm = (ChessMove) Myylex();
11674                 } while (cm == PGNTag || cm == Comment);
11675             }
11676             break;
11677
11678           case WhiteWins:
11679           case BlackWins:
11680           case GameIsDrawn:
11681             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11682                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11683                     != CMAIL_OLD_RESULT) {
11684                     nCmailResults ++ ;
11685                     cmailResult[  CMAIL_MAX_GAMES
11686                                 - gn - 1] = CMAIL_OLD_RESULT;
11687                 }
11688             }
11689             break;
11690
11691           case NormalMove:
11692             /* Only a NormalMove can be at the start of a game
11693              * without a position diagram. */
11694             if (lastLoadGameStart == EndOfFile ) {
11695               gn--;
11696               lastLoadGameStart = MoveNumberOne;
11697             }
11698             break;
11699
11700           default:
11701             break;
11702         }
11703     }
11704
11705     if (appData.debugMode)
11706       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11707
11708     if (cm == XBoardGame) {
11709         /* Skip any header junk before position diagram and/or move 1 */
11710         for (;;) {
11711             yyboardindex = forwardMostMove;
11712             cm = (ChessMove) Myylex();
11713
11714             if (cm == EndOfFile ||
11715                 cm == GNUChessGame || cm == XBoardGame) {
11716                 /* Empty game; pretend end-of-file and handle later */
11717                 cm = EndOfFile;
11718                 break;
11719             }
11720
11721             if (cm == MoveNumberOne || cm == PositionDiagram ||
11722                 cm == PGNTag || cm == Comment)
11723               break;
11724         }
11725     } else if (cm == GNUChessGame) {
11726         if (gameInfo.event != NULL) {
11727             free(gameInfo.event);
11728         }
11729         gameInfo.event = StrSave(yy_text);
11730     }
11731
11732     startedFromSetupPosition = FALSE;
11733     while (cm == PGNTag) {
11734         if (appData.debugMode)
11735           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11736         err = ParsePGNTag(yy_text, &gameInfo);
11737         if (!err) numPGNTags++;
11738
11739         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11740         if(gameInfo.variant != oldVariant) {
11741             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11742             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11743             InitPosition(TRUE);
11744             oldVariant = gameInfo.variant;
11745             if (appData.debugMode)
11746               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11747         }
11748
11749
11750         if (gameInfo.fen != NULL) {
11751           Board initial_position;
11752           startedFromSetupPosition = TRUE;
11753           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11754             Reset(TRUE, TRUE);
11755             DisplayError(_("Bad FEN position in file"), 0);
11756             return FALSE;
11757           }
11758           CopyBoard(boards[0], initial_position);
11759           if (blackPlaysFirst) {
11760             currentMove = forwardMostMove = backwardMostMove = 1;
11761             CopyBoard(boards[1], initial_position);
11762             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11763             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11764             timeRemaining[0][1] = whiteTimeRemaining;
11765             timeRemaining[1][1] = blackTimeRemaining;
11766             if (commentList[0] != NULL) {
11767               commentList[1] = commentList[0];
11768               commentList[0] = NULL;
11769             }
11770           } else {
11771             currentMove = forwardMostMove = backwardMostMove = 0;
11772           }
11773           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11774           {   int i;
11775               initialRulePlies = FENrulePlies;
11776               for( i=0; i< nrCastlingRights; i++ )
11777                   initialRights[i] = initial_position[CASTLING][i];
11778           }
11779           yyboardindex = forwardMostMove;
11780           free(gameInfo.fen);
11781           gameInfo.fen = NULL;
11782         }
11783
11784         yyboardindex = forwardMostMove;
11785         cm = (ChessMove) Myylex();
11786
11787         /* Handle comments interspersed among the tags */
11788         while (cm == Comment) {
11789             char *p;
11790             if (appData.debugMode)
11791               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11792             p = yy_text;
11793             AppendComment(currentMove, p, FALSE);
11794             yyboardindex = forwardMostMove;
11795             cm = (ChessMove) Myylex();
11796         }
11797     }
11798
11799     /* don't rely on existence of Event tag since if game was
11800      * pasted from clipboard the Event tag may not exist
11801      */
11802     if (numPGNTags > 0){
11803         char *tags;
11804         if (gameInfo.variant == VariantNormal) {
11805           VariantClass v = StringToVariant(gameInfo.event);
11806           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11807           if(v < VariantShogi) gameInfo.variant = v;
11808         }
11809         if (!matchMode) {
11810           if( appData.autoDisplayTags ) {
11811             tags = PGNTags(&gameInfo);
11812             TagsPopUp(tags, CmailMsg());
11813             free(tags);
11814           }
11815         }
11816     } else {
11817         /* Make something up, but don't display it now */
11818         SetGameInfo();
11819         TagsPopDown();
11820     }
11821
11822     if (cm == PositionDiagram) {
11823         int i, j;
11824         char *p;
11825         Board initial_position;
11826
11827         if (appData.debugMode)
11828           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11829
11830         if (!startedFromSetupPosition) {
11831             p = yy_text;
11832             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11833               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11834                 switch (*p) {
11835                   case '{':
11836                   case '[':
11837                   case '-':
11838                   case ' ':
11839                   case '\t':
11840                   case '\n':
11841                   case '\r':
11842                     break;
11843                   default:
11844                     initial_position[i][j++] = CharToPiece(*p);
11845                     break;
11846                 }
11847             while (*p == ' ' || *p == '\t' ||
11848                    *p == '\n' || *p == '\r') p++;
11849
11850             if (strncmp(p, "black", strlen("black"))==0)
11851               blackPlaysFirst = TRUE;
11852             else
11853               blackPlaysFirst = FALSE;
11854             startedFromSetupPosition = TRUE;
11855
11856             CopyBoard(boards[0], initial_position);
11857             if (blackPlaysFirst) {
11858                 currentMove = forwardMostMove = backwardMostMove = 1;
11859                 CopyBoard(boards[1], initial_position);
11860                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11861                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11862                 timeRemaining[0][1] = whiteTimeRemaining;
11863                 timeRemaining[1][1] = blackTimeRemaining;
11864                 if (commentList[0] != NULL) {
11865                     commentList[1] = commentList[0];
11866                     commentList[0] = NULL;
11867                 }
11868             } else {
11869                 currentMove = forwardMostMove = backwardMostMove = 0;
11870             }
11871         }
11872         yyboardindex = forwardMostMove;
11873         cm = (ChessMove) Myylex();
11874     }
11875
11876     if (first.pr == NoProc) {
11877         StartChessProgram(&first);
11878     }
11879     InitChessProgram(&first, FALSE);
11880     SendToProgram("force\n", &first);
11881     if (startedFromSetupPosition) {
11882         SendBoard(&first, forwardMostMove);
11883     if (appData.debugMode) {
11884         fprintf(debugFP, "Load Game\n");
11885     }
11886         DisplayBothClocks();
11887     }
11888
11889     /* [HGM] server: flag to write setup moves in broadcast file as one */
11890     loadFlag = appData.suppressLoadMoves;
11891
11892     while (cm == Comment) {
11893         char *p;
11894         if (appData.debugMode)
11895           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11896         p = yy_text;
11897         AppendComment(currentMove, p, FALSE);
11898         yyboardindex = forwardMostMove;
11899         cm = (ChessMove) Myylex();
11900     }
11901
11902     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11903         cm == WhiteWins || cm == BlackWins ||
11904         cm == GameIsDrawn || cm == GameUnfinished) {
11905         DisplayMessage("", _("No moves in game"));
11906         if (cmailMsgLoaded) {
11907             if (appData.debugMode)
11908               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11909             ClearHighlights();
11910             flipView = FALSE;
11911         }
11912         DrawPosition(FALSE, boards[currentMove]);
11913         DisplayBothClocks();
11914         gameMode = EditGame;
11915         ModeHighlight();
11916         gameFileFP = NULL;
11917         cmailOldMove = 0;
11918         return TRUE;
11919     }
11920
11921     // [HGM] PV info: routine tests if comment empty
11922     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11923         DisplayComment(currentMove - 1, commentList[currentMove]);
11924     }
11925     if (!matchMode && appData.timeDelay != 0)
11926       DrawPosition(FALSE, boards[currentMove]);
11927
11928     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11929       programStats.ok_to_send = 1;
11930     }
11931
11932     /* if the first token after the PGN tags is a move
11933      * and not move number 1, retrieve it from the parser
11934      */
11935     if (cm != MoveNumberOne)
11936         LoadGameOneMove(cm);
11937
11938     /* load the remaining moves from the file */
11939     while (LoadGameOneMove(EndOfFile)) {
11940       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11941       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11942     }
11943
11944     /* rewind to the start of the game */
11945     currentMove = backwardMostMove;
11946
11947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11948
11949     if (oldGameMode == AnalyzeFile ||
11950         oldGameMode == AnalyzeMode) {
11951       AnalyzeFileEvent();
11952     }
11953
11954     if (!matchMode && pos >= 0) {
11955         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11956     } else
11957     if (matchMode || appData.timeDelay == 0) {
11958       ToEndEvent();
11959     } else if (appData.timeDelay > 0) {
11960       AutoPlayGameLoop();
11961     }
11962
11963     if (appData.debugMode)
11964         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11965
11966     loadFlag = 0; /* [HGM] true game starts */
11967     return TRUE;
11968 }
11969
11970 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11971 int
11972 ReloadPosition(offset)
11973      int offset;
11974 {
11975     int positionNumber = lastLoadPositionNumber + offset;
11976     if (lastLoadPositionFP == NULL) {
11977         DisplayError(_("No position has been loaded yet"), 0);
11978         return FALSE;
11979     }
11980     if (positionNumber <= 0) {
11981         DisplayError(_("Can't back up any further"), 0);
11982         return FALSE;
11983     }
11984     return LoadPosition(lastLoadPositionFP, positionNumber,
11985                         lastLoadPositionTitle);
11986 }
11987
11988 /* Load the nth position from the given file */
11989 int
11990 LoadPositionFromFile(filename, n, title)
11991      char *filename;
11992      int n;
11993      char *title;
11994 {
11995     FILE *f;
11996     char buf[MSG_SIZ];
11997
11998     if (strcmp(filename, "-") == 0) {
11999         return LoadPosition(stdin, n, "stdin");
12000     } else {
12001         f = fopen(filename, "rb");
12002         if (f == NULL) {
12003             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12004             DisplayError(buf, errno);
12005             return FALSE;
12006         } else {
12007             return LoadPosition(f, n, title);
12008         }
12009     }
12010 }
12011
12012 /* Load the nth position from the given open file, and close it */
12013 int
12014 LoadPosition(f, positionNumber, title)
12015      FILE *f;
12016      int positionNumber;
12017      char *title;
12018 {
12019     char *p, line[MSG_SIZ];
12020     Board initial_position;
12021     int i, j, fenMode, pn;
12022
12023     if (gameMode == Training )
12024         SetTrainingModeOff();
12025
12026     if (gameMode != BeginningOfGame) {
12027         Reset(FALSE, TRUE);
12028     }
12029     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12030         fclose(lastLoadPositionFP);
12031     }
12032     if (positionNumber == 0) positionNumber = 1;
12033     lastLoadPositionFP = f;
12034     lastLoadPositionNumber = positionNumber;
12035     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12036     if (first.pr == NoProc && !appData.noChessProgram) {
12037       StartChessProgram(&first);
12038       InitChessProgram(&first, FALSE);
12039     }
12040     pn = positionNumber;
12041     if (positionNumber < 0) {
12042         /* Negative position number means to seek to that byte offset */
12043         if (fseek(f, -positionNumber, 0) == -1) {
12044             DisplayError(_("Can't seek on position file"), 0);
12045             return FALSE;
12046         };
12047         pn = 1;
12048     } else {
12049         if (fseek(f, 0, 0) == -1) {
12050             if (f == lastLoadPositionFP ?
12051                 positionNumber == lastLoadPositionNumber + 1 :
12052                 positionNumber == 1) {
12053                 pn = 1;
12054             } else {
12055                 DisplayError(_("Can't seek on position file"), 0);
12056                 return FALSE;
12057             }
12058         }
12059     }
12060     /* See if this file is FEN or old-style xboard */
12061     if (fgets(line, MSG_SIZ, f) == NULL) {
12062         DisplayError(_("Position not found in file"), 0);
12063         return FALSE;
12064     }
12065     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12066     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12067
12068     if (pn >= 2) {
12069         if (fenMode || line[0] == '#') pn--;
12070         while (pn > 0) {
12071             /* skip positions before number pn */
12072             if (fgets(line, MSG_SIZ, f) == NULL) {
12073                 Reset(TRUE, TRUE);
12074                 DisplayError(_("Position not found in file"), 0);
12075                 return FALSE;
12076             }
12077             if (fenMode || line[0] == '#') pn--;
12078         }
12079     }
12080
12081     if (fenMode) {
12082         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12083             DisplayError(_("Bad FEN position in file"), 0);
12084             return FALSE;
12085         }
12086     } else {
12087         (void) fgets(line, MSG_SIZ, f);
12088         (void) fgets(line, MSG_SIZ, f);
12089
12090         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12091             (void) fgets(line, MSG_SIZ, f);
12092             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12093                 if (*p == ' ')
12094                   continue;
12095                 initial_position[i][j++] = CharToPiece(*p);
12096             }
12097         }
12098
12099         blackPlaysFirst = FALSE;
12100         if (!feof(f)) {
12101             (void) fgets(line, MSG_SIZ, f);
12102             if (strncmp(line, "black", strlen("black"))==0)
12103               blackPlaysFirst = TRUE;
12104         }
12105     }
12106     startedFromSetupPosition = TRUE;
12107
12108     CopyBoard(boards[0], initial_position);
12109     if (blackPlaysFirst) {
12110         currentMove = forwardMostMove = backwardMostMove = 1;
12111         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12112         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12113         CopyBoard(boards[1], initial_position);
12114         DisplayMessage("", _("Black to play"));
12115     } else {
12116         currentMove = forwardMostMove = backwardMostMove = 0;
12117         DisplayMessage("", _("White to play"));
12118     }
12119     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12120     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12121         SendToProgram("force\n", &first);
12122         SendBoard(&first, forwardMostMove);
12123     }
12124     if (appData.debugMode) {
12125 int i, j;
12126   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12127   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12128         fprintf(debugFP, "Load Position\n");
12129     }
12130
12131     if (positionNumber > 1) {
12132       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12133         DisplayTitle(line);
12134     } else {
12135         DisplayTitle(title);
12136     }
12137     gameMode = EditGame;
12138     ModeHighlight();
12139     ResetClocks();
12140     timeRemaining[0][1] = whiteTimeRemaining;
12141     timeRemaining[1][1] = blackTimeRemaining;
12142     DrawPosition(FALSE, boards[currentMove]);
12143
12144     return TRUE;
12145 }
12146
12147
12148 void
12149 CopyPlayerNameIntoFileName(dest, src)
12150      char **dest, *src;
12151 {
12152     while (*src != NULLCHAR && *src != ',') {
12153         if (*src == ' ') {
12154             *(*dest)++ = '_';
12155             src++;
12156         } else {
12157             *(*dest)++ = *src++;
12158         }
12159     }
12160 }
12161
12162 char *DefaultFileName(ext)
12163      char *ext;
12164 {
12165     static char def[MSG_SIZ];
12166     char *p;
12167
12168     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12169         p = def;
12170         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12171         *p++ = '-';
12172         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12173         *p++ = '.';
12174         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12175     } else {
12176         def[0] = NULLCHAR;
12177     }
12178     return def;
12179 }
12180
12181 /* Save the current game to the given file */
12182 int
12183 SaveGameToFile(filename, append)
12184      char *filename;
12185      int append;
12186 {
12187     FILE *f;
12188     char buf[MSG_SIZ];
12189     int result, i, t,tot=0;
12190
12191     if (strcmp(filename, "-") == 0) {
12192         return SaveGame(stdout, 0, NULL);
12193     } else {
12194         for(i=0; i<10; i++) { // upto 10 tries
12195              f = fopen(filename, append ? "a" : "w");
12196              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12197              if(f || errno != 13) break;
12198              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12199              tot += t;
12200         }
12201         if (f == NULL) {
12202             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12203             DisplayError(buf, errno);
12204             return FALSE;
12205         } else {
12206             safeStrCpy(buf, lastMsg, MSG_SIZ);
12207             DisplayMessage(_("Waiting for access to save file"), "");
12208             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12209             DisplayMessage(_("Saving game"), "");
12210             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
12211             result = SaveGame(f, 0, NULL);
12212             DisplayMessage(buf, "");
12213             return result;
12214         }
12215     }
12216 }
12217
12218 char *
12219 SavePart(str)
12220      char *str;
12221 {
12222     static char buf[MSG_SIZ];
12223     char *p;
12224
12225     p = strchr(str, ' ');
12226     if (p == NULL) return str;
12227     strncpy(buf, str, p - str);
12228     buf[p - str] = NULLCHAR;
12229     return buf;
12230 }
12231
12232 #define PGN_MAX_LINE 75
12233
12234 #define PGN_SIDE_WHITE  0
12235 #define PGN_SIDE_BLACK  1
12236
12237 /* [AS] */
12238 static int FindFirstMoveOutOfBook( int side )
12239 {
12240     int result = -1;
12241
12242     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12243         int index = backwardMostMove;
12244         int has_book_hit = 0;
12245
12246         if( (index % 2) != side ) {
12247             index++;
12248         }
12249
12250         while( index < forwardMostMove ) {
12251             /* Check to see if engine is in book */
12252             int depth = pvInfoList[index].depth;
12253             int score = pvInfoList[index].score;
12254             int in_book = 0;
12255
12256             if( depth <= 2 ) {
12257                 in_book = 1;
12258             }
12259             else if( score == 0 && depth == 63 ) {
12260                 in_book = 1; /* Zappa */
12261             }
12262             else if( score == 2 && depth == 99 ) {
12263                 in_book = 1; /* Abrok */
12264             }
12265
12266             has_book_hit += in_book;
12267
12268             if( ! in_book ) {
12269                 result = index;
12270
12271                 break;
12272             }
12273
12274             index += 2;
12275         }
12276     }
12277
12278     return result;
12279 }
12280
12281 /* [AS] */
12282 void GetOutOfBookInfo( char * buf )
12283 {
12284     int oob[2];
12285     int i;
12286     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12287
12288     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12289     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12290
12291     *buf = '\0';
12292
12293     if( oob[0] >= 0 || oob[1] >= 0 ) {
12294         for( i=0; i<2; i++ ) {
12295             int idx = oob[i];
12296
12297             if( idx >= 0 ) {
12298                 if( i > 0 && oob[0] >= 0 ) {
12299                     strcat( buf, "   " );
12300                 }
12301
12302                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12303                 sprintf( buf+strlen(buf), "%s%.2f",
12304                     pvInfoList[idx].score >= 0 ? "+" : "",
12305                     pvInfoList[idx].score / 100.0 );
12306             }
12307         }
12308     }
12309 }
12310
12311 /* Save game in PGN style and close the file */
12312 int
12313 SaveGamePGN(f)
12314      FILE *f;
12315 {
12316     int i, offset, linelen, newblock;
12317     time_t tm;
12318 //    char *movetext;
12319     char numtext[32];
12320     int movelen, numlen, blank;
12321     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12322
12323     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12324
12325     tm = time((time_t *) NULL);
12326
12327     PrintPGNTags(f, &gameInfo);
12328
12329     if (backwardMostMove > 0 || startedFromSetupPosition) {
12330         char *fen = PositionToFEN(backwardMostMove, NULL);
12331         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12332         fprintf(f, "\n{--------------\n");
12333         PrintPosition(f, backwardMostMove);
12334         fprintf(f, "--------------}\n");
12335         free(fen);
12336     }
12337     else {
12338         /* [AS] Out of book annotation */
12339         if( appData.saveOutOfBookInfo ) {
12340             char buf[64];
12341
12342             GetOutOfBookInfo( buf );
12343
12344             if( buf[0] != '\0' ) {
12345                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12346             }
12347         }
12348
12349         fprintf(f, "\n");
12350     }
12351
12352     i = backwardMostMove;
12353     linelen = 0;
12354     newblock = TRUE;
12355
12356     while (i < forwardMostMove) {
12357         /* Print comments preceding this move */
12358         if (commentList[i] != NULL) {
12359             if (linelen > 0) fprintf(f, "\n");
12360             fprintf(f, "%s", commentList[i]);
12361             linelen = 0;
12362             newblock = TRUE;
12363         }
12364
12365         /* Format move number */
12366         if ((i % 2) == 0)
12367           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12368         else
12369           if (newblock)
12370             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12371           else
12372             numtext[0] = NULLCHAR;
12373
12374         numlen = strlen(numtext);
12375         newblock = FALSE;
12376
12377         /* Print move number */
12378         blank = linelen > 0 && numlen > 0;
12379         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12380             fprintf(f, "\n");
12381             linelen = 0;
12382             blank = 0;
12383         }
12384         if (blank) {
12385             fprintf(f, " ");
12386             linelen++;
12387         }
12388         fprintf(f, "%s", numtext);
12389         linelen += numlen;
12390
12391         /* Get move */
12392         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12393         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12394
12395         /* Print move */
12396         blank = linelen > 0 && movelen > 0;
12397         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12398             fprintf(f, "\n");
12399             linelen = 0;
12400             blank = 0;
12401         }
12402         if (blank) {
12403             fprintf(f, " ");
12404             linelen++;
12405         }
12406         fprintf(f, "%s", move_buffer);
12407         linelen += movelen;
12408
12409         /* [AS] Add PV info if present */
12410         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12411             /* [HGM] add time */
12412             char buf[MSG_SIZ]; int seconds;
12413
12414             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12415
12416             if( seconds <= 0)
12417               buf[0] = 0;
12418             else
12419               if( seconds < 30 )
12420                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12421               else
12422                 {
12423                   seconds = (seconds + 4)/10; // round to full seconds
12424                   if( seconds < 60 )
12425                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12426                   else
12427                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12428                 }
12429
12430             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12431                       pvInfoList[i].score >= 0 ? "+" : "",
12432                       pvInfoList[i].score / 100.0,
12433                       pvInfoList[i].depth,
12434                       buf );
12435
12436             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12437
12438             /* Print score/depth */
12439             blank = linelen > 0 && movelen > 0;
12440             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12441                 fprintf(f, "\n");
12442                 linelen = 0;
12443                 blank = 0;
12444             }
12445             if (blank) {
12446                 fprintf(f, " ");
12447                 linelen++;
12448             }
12449             fprintf(f, "%s", move_buffer);
12450             linelen += movelen;
12451         }
12452
12453         i++;
12454     }
12455
12456     /* Start a new line */
12457     if (linelen > 0) fprintf(f, "\n");
12458
12459     /* Print comments after last move */
12460     if (commentList[i] != NULL) {
12461         fprintf(f, "%s\n", commentList[i]);
12462     }
12463
12464     /* Print result */
12465     if (gameInfo.resultDetails != NULL &&
12466         gameInfo.resultDetails[0] != NULLCHAR) {
12467         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12468                 PGNResult(gameInfo.result));
12469     } else {
12470         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12471     }
12472
12473     fclose(f);
12474     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12475     return TRUE;
12476 }
12477
12478 /* Save game in old style and close the file */
12479 int
12480 SaveGameOldStyle(f)
12481      FILE *f;
12482 {
12483     int i, offset;
12484     time_t tm;
12485
12486     tm = time((time_t *) NULL);
12487
12488     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12489     PrintOpponents(f);
12490
12491     if (backwardMostMove > 0 || startedFromSetupPosition) {
12492         fprintf(f, "\n[--------------\n");
12493         PrintPosition(f, backwardMostMove);
12494         fprintf(f, "--------------]\n");
12495     } else {
12496         fprintf(f, "\n");
12497     }
12498
12499     i = backwardMostMove;
12500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12501
12502     while (i < forwardMostMove) {
12503         if (commentList[i] != NULL) {
12504             fprintf(f, "[%s]\n", commentList[i]);
12505         }
12506
12507         if ((i % 2) == 1) {
12508             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12509             i++;
12510         } else {
12511             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12512             i++;
12513             if (commentList[i] != NULL) {
12514                 fprintf(f, "\n");
12515                 continue;
12516             }
12517             if (i >= forwardMostMove) {
12518                 fprintf(f, "\n");
12519                 break;
12520             }
12521             fprintf(f, "%s\n", parseList[i]);
12522             i++;
12523         }
12524     }
12525
12526     if (commentList[i] != NULL) {
12527         fprintf(f, "[%s]\n", commentList[i]);
12528     }
12529
12530     /* This isn't really the old style, but it's close enough */
12531     if (gameInfo.resultDetails != NULL &&
12532         gameInfo.resultDetails[0] != NULLCHAR) {
12533         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12534                 gameInfo.resultDetails);
12535     } else {
12536         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12537     }
12538
12539     fclose(f);
12540     return TRUE;
12541 }
12542
12543 /* Save the current game to open file f and close the file */
12544 int
12545 SaveGame(f, dummy, dummy2)
12546      FILE *f;
12547      int dummy;
12548      char *dummy2;
12549 {
12550     if (gameMode == EditPosition) EditPositionDone(TRUE);
12551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12552     if (appData.oldSaveStyle)
12553       return SaveGameOldStyle(f);
12554     else
12555       return SaveGamePGN(f);
12556 }
12557
12558 /* Save the current position to the given file */
12559 int
12560 SavePositionToFile(filename)
12561      char *filename;
12562 {
12563     FILE *f;
12564     char buf[MSG_SIZ];
12565
12566     if (strcmp(filename, "-") == 0) {
12567         return SavePosition(stdout, 0, NULL);
12568     } else {
12569         f = fopen(filename, "a");
12570         if (f == NULL) {
12571             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12572             DisplayError(buf, errno);
12573             return FALSE;
12574         } else {
12575             safeStrCpy(buf, lastMsg, MSG_SIZ);
12576             DisplayMessage(_("Waiting for access to save file"), "");
12577             flock(fileno(f), LOCK_EX); // [HGM] lock
12578             DisplayMessage(_("Saving position"), "");
12579             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12580             SavePosition(f, 0, NULL);
12581             DisplayMessage(buf, "");
12582             return TRUE;
12583         }
12584     }
12585 }
12586
12587 /* Save the current position to the given open file and close the file */
12588 int
12589 SavePosition(f, dummy, dummy2)
12590      FILE *f;
12591      int dummy;
12592      char *dummy2;
12593 {
12594     time_t tm;
12595     char *fen;
12596
12597     if (gameMode == EditPosition) EditPositionDone(TRUE);
12598     if (appData.oldSaveStyle) {
12599         tm = time((time_t *) NULL);
12600
12601         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12602         PrintOpponents(f);
12603         fprintf(f, "[--------------\n");
12604         PrintPosition(f, currentMove);
12605         fprintf(f, "--------------]\n");
12606     } else {
12607         fen = PositionToFEN(currentMove, NULL);
12608         fprintf(f, "%s\n", fen);
12609         free(fen);
12610     }
12611     fclose(f);
12612     return TRUE;
12613 }
12614
12615 void
12616 ReloadCmailMsgEvent(unregister)
12617      int unregister;
12618 {
12619 #if !WIN32
12620     static char *inFilename = NULL;
12621     static char *outFilename;
12622     int i;
12623     struct stat inbuf, outbuf;
12624     int status;
12625
12626     /* Any registered moves are unregistered if unregister is set, */
12627     /* i.e. invoked by the signal handler */
12628     if (unregister) {
12629         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12630             cmailMoveRegistered[i] = FALSE;
12631             if (cmailCommentList[i] != NULL) {
12632                 free(cmailCommentList[i]);
12633                 cmailCommentList[i] = NULL;
12634             }
12635         }
12636         nCmailMovesRegistered = 0;
12637     }
12638
12639     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12640         cmailResult[i] = CMAIL_NOT_RESULT;
12641     }
12642     nCmailResults = 0;
12643
12644     if (inFilename == NULL) {
12645         /* Because the filenames are static they only get malloced once  */
12646         /* and they never get freed                                      */
12647         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12648         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12649
12650         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12651         sprintf(outFilename, "%s.out", appData.cmailGameName);
12652     }
12653
12654     status = stat(outFilename, &outbuf);
12655     if (status < 0) {
12656         cmailMailedMove = FALSE;
12657     } else {
12658         status = stat(inFilename, &inbuf);
12659         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12660     }
12661
12662     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12663        counts the games, notes how each one terminated, etc.
12664
12665        It would be nice to remove this kludge and instead gather all
12666        the information while building the game list.  (And to keep it
12667        in the game list nodes instead of having a bunch of fixed-size
12668        parallel arrays.)  Note this will require getting each game's
12669        termination from the PGN tags, as the game list builder does
12670        not process the game moves.  --mann
12671        */
12672     cmailMsgLoaded = TRUE;
12673     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12674
12675     /* Load first game in the file or popup game menu */
12676     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12677
12678 #endif /* !WIN32 */
12679     return;
12680 }
12681
12682 int
12683 RegisterMove()
12684 {
12685     FILE *f;
12686     char string[MSG_SIZ];
12687
12688     if (   cmailMailedMove
12689         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12690         return TRUE;            /* Allow free viewing  */
12691     }
12692
12693     /* Unregister move to ensure that we don't leave RegisterMove        */
12694     /* with the move registered when the conditions for registering no   */
12695     /* longer hold                                                       */
12696     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12697         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12698         nCmailMovesRegistered --;
12699
12700         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12701           {
12702               free(cmailCommentList[lastLoadGameNumber - 1]);
12703               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12704           }
12705     }
12706
12707     if (cmailOldMove == -1) {
12708         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12709         return FALSE;
12710     }
12711
12712     if (currentMove > cmailOldMove + 1) {
12713         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12714         return FALSE;
12715     }
12716
12717     if (currentMove < cmailOldMove) {
12718         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12719         return FALSE;
12720     }
12721
12722     if (forwardMostMove > currentMove) {
12723         /* Silently truncate extra moves */
12724         TruncateGame();
12725     }
12726
12727     if (   (currentMove == cmailOldMove + 1)
12728         || (   (currentMove == cmailOldMove)
12729             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12730                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12731         if (gameInfo.result != GameUnfinished) {
12732             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12733         }
12734
12735         if (commentList[currentMove] != NULL) {
12736             cmailCommentList[lastLoadGameNumber - 1]
12737               = StrSave(commentList[currentMove]);
12738         }
12739         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12740
12741         if (appData.debugMode)
12742           fprintf(debugFP, "Saving %s for game %d\n",
12743                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12744
12745         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12746
12747         f = fopen(string, "w");
12748         if (appData.oldSaveStyle) {
12749             SaveGameOldStyle(f); /* also closes the file */
12750
12751             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12752             f = fopen(string, "w");
12753             SavePosition(f, 0, NULL); /* also closes the file */
12754         } else {
12755             fprintf(f, "{--------------\n");
12756             PrintPosition(f, currentMove);
12757             fprintf(f, "--------------}\n\n");
12758
12759             SaveGame(f, 0, NULL); /* also closes the file*/
12760         }
12761
12762         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12763         nCmailMovesRegistered ++;
12764     } else if (nCmailGames == 1) {
12765         DisplayError(_("You have not made a move yet"), 0);
12766         return FALSE;
12767     }
12768
12769     return TRUE;
12770 }
12771
12772 void
12773 MailMoveEvent()
12774 {
12775 #if !WIN32
12776     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12777     FILE *commandOutput;
12778     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12779     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12780     int nBuffers;
12781     int i;
12782     int archived;
12783     char *arcDir;
12784
12785     if (! cmailMsgLoaded) {
12786         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12787         return;
12788     }
12789
12790     if (nCmailGames == nCmailResults) {
12791         DisplayError(_("No unfinished games"), 0);
12792         return;
12793     }
12794
12795 #if CMAIL_PROHIBIT_REMAIL
12796     if (cmailMailedMove) {
12797       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);
12798         DisplayError(msg, 0);
12799         return;
12800     }
12801 #endif
12802
12803     if (! (cmailMailedMove || RegisterMove())) return;
12804
12805     if (   cmailMailedMove
12806         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12807       snprintf(string, MSG_SIZ, partCommandString,
12808                appData.debugMode ? " -v" : "", appData.cmailGameName);
12809         commandOutput = popen(string, "r");
12810
12811         if (commandOutput == NULL) {
12812             DisplayError(_("Failed to invoke cmail"), 0);
12813         } else {
12814             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12815                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12816             }
12817             if (nBuffers > 1) {
12818                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12819                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12820                 nBytes = MSG_SIZ - 1;
12821             } else {
12822                 (void) memcpy(msg, buffer, nBytes);
12823             }
12824             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12825
12826             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12827                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12828
12829                 archived = TRUE;
12830                 for (i = 0; i < nCmailGames; i ++) {
12831                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12832                         archived = FALSE;
12833                     }
12834                 }
12835                 if (   archived
12836                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12837                         != NULL)) {
12838                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12839                            arcDir,
12840                            appData.cmailGameName,
12841                            gameInfo.date);
12842                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12843                     cmailMsgLoaded = FALSE;
12844                 }
12845             }
12846
12847             DisplayInformation(msg);
12848             pclose(commandOutput);
12849         }
12850     } else {
12851         if ((*cmailMsg) != '\0') {
12852             DisplayInformation(cmailMsg);
12853         }
12854     }
12855
12856     return;
12857 #endif /* !WIN32 */
12858 }
12859
12860 char *
12861 CmailMsg()
12862 {
12863 #if WIN32
12864     return NULL;
12865 #else
12866     int  prependComma = 0;
12867     char number[5];
12868     char string[MSG_SIZ];       /* Space for game-list */
12869     int  i;
12870
12871     if (!cmailMsgLoaded) return "";
12872
12873     if (cmailMailedMove) {
12874       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12875     } else {
12876         /* Create a list of games left */
12877       snprintf(string, MSG_SIZ, "[");
12878         for (i = 0; i < nCmailGames; i ++) {
12879             if (! (   cmailMoveRegistered[i]
12880                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12881                 if (prependComma) {
12882                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12883                 } else {
12884                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12885                     prependComma = 1;
12886                 }
12887
12888                 strcat(string, number);
12889             }
12890         }
12891         strcat(string, "]");
12892
12893         if (nCmailMovesRegistered + nCmailResults == 0) {
12894             switch (nCmailGames) {
12895               case 1:
12896                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12897                 break;
12898
12899               case 2:
12900                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12901                 break;
12902
12903               default:
12904                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12905                          nCmailGames);
12906                 break;
12907             }
12908         } else {
12909             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12910               case 1:
12911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12912                          string);
12913                 break;
12914
12915               case 0:
12916                 if (nCmailResults == nCmailGames) {
12917                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12918                 } else {
12919                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12920                 }
12921                 break;
12922
12923               default:
12924                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12925                          string);
12926             }
12927         }
12928     }
12929     return cmailMsg;
12930 #endif /* WIN32 */
12931 }
12932
12933 void
12934 ResetGameEvent()
12935 {
12936     if (gameMode == Training)
12937       SetTrainingModeOff();
12938
12939     Reset(TRUE, TRUE);
12940     cmailMsgLoaded = FALSE;
12941     if (appData.icsActive) {
12942       SendToICS(ics_prefix);
12943       SendToICS("refresh\n");
12944     }
12945 }
12946
12947 void
12948 ExitEvent(status)
12949      int status;
12950 {
12951     exiting++;
12952     if (exiting > 2) {
12953       /* Give up on clean exit */
12954       exit(status);
12955     }
12956     if (exiting > 1) {
12957       /* Keep trying for clean exit */
12958       return;
12959     }
12960
12961     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12962
12963     if (telnetISR != NULL) {
12964       RemoveInputSource(telnetISR);
12965     }
12966     if (icsPR != NoProc) {
12967       DestroyChildProcess(icsPR, TRUE);
12968     }
12969
12970     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12971     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12972
12973     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12974     /* make sure this other one finishes before killing it!                  */
12975     if(endingGame) { int count = 0;
12976         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12977         while(endingGame && count++ < 10) DoSleep(1);
12978         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12979     }
12980
12981     /* Kill off chess programs */
12982     if (first.pr != NoProc) {
12983         ExitAnalyzeMode();
12984
12985         DoSleep( appData.delayBeforeQuit );
12986         SendToProgram("quit\n", &first);
12987         DoSleep( appData.delayAfterQuit );
12988         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12989     }
12990     if (second.pr != NoProc) {
12991         DoSleep( appData.delayBeforeQuit );
12992         SendToProgram("quit\n", &second);
12993         DoSleep( appData.delayAfterQuit );
12994         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12995     }
12996     if (first.isr != NULL) {
12997         RemoveInputSource(first.isr);
12998     }
12999     if (second.isr != NULL) {
13000         RemoveInputSource(second.isr);
13001     }
13002
13003     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13004     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13005
13006     ShutDownFrontEnd();
13007     exit(status);
13008 }
13009
13010 void
13011 PauseEvent()
13012 {
13013     if (appData.debugMode)
13014         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13015     if (pausing) {
13016         pausing = FALSE;
13017         ModeHighlight();
13018         if (gameMode == MachinePlaysWhite ||
13019             gameMode == MachinePlaysBlack) {
13020             StartClocks();
13021         } else {
13022             DisplayBothClocks();
13023         }
13024         if (gameMode == PlayFromGameFile) {
13025             if (appData.timeDelay >= 0)
13026                 AutoPlayGameLoop();
13027         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13028             Reset(FALSE, TRUE);
13029             SendToICS(ics_prefix);
13030             SendToICS("refresh\n");
13031         } else if (currentMove < forwardMostMove) {
13032             ForwardInner(forwardMostMove);
13033         }
13034         pauseExamInvalid = FALSE;
13035     } else {
13036         switch (gameMode) {
13037           default:
13038             return;
13039           case IcsExamining:
13040             pauseExamForwardMostMove = forwardMostMove;
13041             pauseExamInvalid = FALSE;
13042             /* fall through */
13043           case IcsObserving:
13044           case IcsPlayingWhite:
13045           case IcsPlayingBlack:
13046             pausing = TRUE;
13047             ModeHighlight();
13048             return;
13049           case PlayFromGameFile:
13050             (void) StopLoadGameTimer();
13051             pausing = TRUE;
13052             ModeHighlight();
13053             break;
13054           case BeginningOfGame:
13055             if (appData.icsActive) return;
13056             /* else fall through */
13057           case MachinePlaysWhite:
13058           case MachinePlaysBlack:
13059           case TwoMachinesPlay:
13060             if (forwardMostMove == 0)
13061               return;           /* don't pause if no one has moved */
13062             if ((gameMode == MachinePlaysWhite &&
13063                  !WhiteOnMove(forwardMostMove)) ||
13064                 (gameMode == MachinePlaysBlack &&
13065                  WhiteOnMove(forwardMostMove))) {
13066                 StopClocks();
13067             }
13068             pausing = TRUE;
13069             ModeHighlight();
13070             break;
13071         }
13072     }
13073 }
13074
13075 void
13076 EditCommentEvent()
13077 {
13078     char title[MSG_SIZ];
13079
13080     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13081       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13082     } else {
13083       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13084                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13085                parseList[currentMove - 1]);
13086     }
13087
13088     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13089 }
13090
13091
13092 void
13093 EditTagsEvent()
13094 {
13095     char *tags = PGNTags(&gameInfo);
13096     bookUp = FALSE;
13097     EditTagsPopUp(tags, NULL);
13098     free(tags);
13099 }
13100
13101 void
13102 AnalyzeModeEvent()
13103 {
13104     if (appData.noChessProgram || gameMode == AnalyzeMode)
13105       return;
13106
13107     if (gameMode != AnalyzeFile) {
13108         if (!appData.icsEngineAnalyze) {
13109                EditGameEvent();
13110                if (gameMode != EditGame) return;
13111         }
13112         ResurrectChessProgram();
13113         SendToProgram("analyze\n", &first);
13114         first.analyzing = TRUE;
13115         /*first.maybeThinking = TRUE;*/
13116         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13117         EngineOutputPopUp();
13118     }
13119     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13120     pausing = FALSE;
13121     ModeHighlight();
13122     SetGameInfo();
13123
13124     StartAnalysisClock();
13125     GetTimeMark(&lastNodeCountTime);
13126     lastNodeCount = 0;
13127 }
13128
13129 void
13130 AnalyzeFileEvent()
13131 {
13132     if (appData.noChessProgram || gameMode == AnalyzeFile)
13133       return;
13134
13135     if (gameMode != AnalyzeMode) {
13136         EditGameEvent();
13137         if (gameMode != EditGame) return;
13138         ResurrectChessProgram();
13139         SendToProgram("analyze\n", &first);
13140         first.analyzing = TRUE;
13141         /*first.maybeThinking = TRUE;*/
13142         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13143         EngineOutputPopUp();
13144     }
13145     gameMode = AnalyzeFile;
13146     pausing = FALSE;
13147     ModeHighlight();
13148     SetGameInfo();
13149
13150     StartAnalysisClock();
13151     GetTimeMark(&lastNodeCountTime);
13152     lastNodeCount = 0;
13153     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13154 }
13155
13156 void
13157 MachineWhiteEvent()
13158 {
13159     char buf[MSG_SIZ];
13160     char *bookHit = NULL;
13161
13162     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13163       return;
13164
13165
13166     if (gameMode == PlayFromGameFile ||
13167         gameMode == TwoMachinesPlay  ||
13168         gameMode == Training         ||
13169         gameMode == AnalyzeMode      ||
13170         gameMode == EndOfGame)
13171         EditGameEvent();
13172
13173     if (gameMode == EditPosition)
13174         EditPositionDone(TRUE);
13175
13176     if (!WhiteOnMove(currentMove)) {
13177         DisplayError(_("It is not White's turn"), 0);
13178         return;
13179     }
13180
13181     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13182       ExitAnalyzeMode();
13183
13184     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13185         gameMode == AnalyzeFile)
13186         TruncateGame();
13187
13188     ResurrectChessProgram();    /* in case it isn't running */
13189     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13190         gameMode = MachinePlaysWhite;
13191         ResetClocks();
13192     } else
13193     gameMode = MachinePlaysWhite;
13194     pausing = FALSE;
13195     ModeHighlight();
13196     SetGameInfo();
13197     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13198     DisplayTitle(buf);
13199     if (first.sendName) {
13200       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13201       SendToProgram(buf, &first);
13202     }
13203     if (first.sendTime) {
13204       if (first.useColors) {
13205         SendToProgram("black\n", &first); /*gnu kludge*/
13206       }
13207       SendTimeRemaining(&first, TRUE);
13208     }
13209     if (first.useColors) {
13210       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13211     }
13212     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13213     SetMachineThinkingEnables();
13214     first.maybeThinking = TRUE;
13215     StartClocks();
13216     firstMove = FALSE;
13217
13218     if (appData.autoFlipView && !flipView) {
13219       flipView = !flipView;
13220       DrawPosition(FALSE, NULL);
13221       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13222     }
13223
13224     if(bookHit) { // [HGM] book: simulate book reply
13225         static char bookMove[MSG_SIZ]; // a bit generous?
13226
13227         programStats.nodes = programStats.depth = programStats.time =
13228         programStats.score = programStats.got_only_move = 0;
13229         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13230
13231         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13232         strcat(bookMove, bookHit);
13233         HandleMachineMove(bookMove, &first);
13234     }
13235 }
13236
13237 void
13238 MachineBlackEvent()
13239 {
13240   char buf[MSG_SIZ];
13241   char *bookHit = NULL;
13242
13243     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13244         return;
13245
13246
13247     if (gameMode == PlayFromGameFile ||
13248         gameMode == TwoMachinesPlay  ||
13249         gameMode == Training         ||
13250         gameMode == AnalyzeMode      ||
13251         gameMode == EndOfGame)
13252         EditGameEvent();
13253
13254     if (gameMode == EditPosition)
13255         EditPositionDone(TRUE);
13256
13257     if (WhiteOnMove(currentMove)) {
13258         DisplayError(_("It is not Black's turn"), 0);
13259         return;
13260     }
13261
13262     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13263       ExitAnalyzeMode();
13264
13265     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13266         gameMode == AnalyzeFile)
13267         TruncateGame();
13268
13269     ResurrectChessProgram();    /* in case it isn't running */
13270     gameMode = MachinePlaysBlack;
13271     pausing = FALSE;
13272     ModeHighlight();
13273     SetGameInfo();
13274     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13275     DisplayTitle(buf);
13276     if (first.sendName) {
13277       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13278       SendToProgram(buf, &first);
13279     }
13280     if (first.sendTime) {
13281       if (first.useColors) {
13282         SendToProgram("white\n", &first); /*gnu kludge*/
13283       }
13284       SendTimeRemaining(&first, FALSE);
13285     }
13286     if (first.useColors) {
13287       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13288     }
13289     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13290     SetMachineThinkingEnables();
13291     first.maybeThinking = TRUE;
13292     StartClocks();
13293
13294     if (appData.autoFlipView && flipView) {
13295       flipView = !flipView;
13296       DrawPosition(FALSE, NULL);
13297       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13298     }
13299     if(bookHit) { // [HGM] book: simulate book reply
13300         static char bookMove[MSG_SIZ]; // a bit generous?
13301
13302         programStats.nodes = programStats.depth = programStats.time =
13303         programStats.score = programStats.got_only_move = 0;
13304         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13305
13306         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13307         strcat(bookMove, bookHit);
13308         HandleMachineMove(bookMove, &first);
13309     }
13310 }
13311
13312
13313 void
13314 DisplayTwoMachinesTitle()
13315 {
13316     char buf[MSG_SIZ];
13317     if (appData.matchGames > 0) {
13318         if(appData.tourneyFile[0]) {
13319           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13320                    gameInfo.white, gameInfo.black,
13321                    nextGame+1, appData.matchGames+1,
13322                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13323         } else 
13324         if (first.twoMachinesColor[0] == 'w') {
13325           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13326                    gameInfo.white, gameInfo.black,
13327                    first.matchWins, second.matchWins,
13328                    matchGame - 1 - (first.matchWins + second.matchWins));
13329         } else {
13330           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13331                    gameInfo.white, gameInfo.black,
13332                    second.matchWins, first.matchWins,
13333                    matchGame - 1 - (first.matchWins + second.matchWins));
13334         }
13335     } else {
13336       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13337     }
13338     DisplayTitle(buf);
13339 }
13340
13341 void
13342 SettingsMenuIfReady()
13343 {
13344   if (second.lastPing != second.lastPong) {
13345     DisplayMessage("", _("Waiting for second chess program"));
13346     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13347     return;
13348   }
13349   ThawUI();
13350   DisplayMessage("", "");
13351   SettingsPopUp(&second);
13352 }
13353
13354 int
13355 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13356 {
13357     char buf[MSG_SIZ];
13358     if (cps->pr == NoProc) {
13359         StartChessProgram(cps);
13360         if (cps->protocolVersion == 1) {
13361           retry();
13362         } else {
13363           /* kludge: allow timeout for initial "feature" command */
13364           FreezeUI();
13365           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13366           DisplayMessage("", buf);
13367           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13368         }
13369         return 1;
13370     }
13371     return 0;
13372 }
13373
13374 void
13375 TwoMachinesEvent P((void))
13376 {
13377     int i;
13378     char buf[MSG_SIZ];
13379     ChessProgramState *onmove;
13380     char *bookHit = NULL;
13381     static int stalling = 0;
13382     TimeMark now;
13383     long wait;
13384
13385     if (appData.noChessProgram) return;
13386
13387     switch (gameMode) {
13388       case TwoMachinesPlay:
13389         return;
13390       case MachinePlaysWhite:
13391       case MachinePlaysBlack:
13392         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13393             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13394             return;
13395         }
13396         /* fall through */
13397       case BeginningOfGame:
13398       case PlayFromGameFile:
13399       case EndOfGame:
13400         EditGameEvent();
13401         if (gameMode != EditGame) return;
13402         break;
13403       case EditPosition:
13404         EditPositionDone(TRUE);
13405         break;
13406       case AnalyzeMode:
13407       case AnalyzeFile:
13408         ExitAnalyzeMode();
13409         break;
13410       case EditGame:
13411       default:
13412         break;
13413     }
13414
13415 //    forwardMostMove = currentMove;
13416     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13417
13418     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13419
13420     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13421     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13422       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13423       return;
13424     }
13425     if(!stalling) {
13426       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13427       SendToProgram("force\n", &second);
13428       stalling = 1;
13429       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13430       return;
13431     }
13432     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13433     if(appData.matchPause>10000 || appData.matchPause<10)
13434                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13435     wait = SubtractTimeMarks(&now, &pauseStart);
13436     if(wait < appData.matchPause) {
13437         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13438         return;
13439     }
13440     stalling = 0;
13441     DisplayMessage("", "");
13442     if (startedFromSetupPosition) {
13443         SendBoard(&second, backwardMostMove);
13444     if (appData.debugMode) {
13445         fprintf(debugFP, "Two Machines\n");
13446     }
13447     }
13448     for (i = backwardMostMove; i < forwardMostMove; i++) {
13449         SendMoveToProgram(i, &second);
13450     }
13451
13452     gameMode = TwoMachinesPlay;
13453     pausing = FALSE;
13454     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13455     SetGameInfo();
13456     DisplayTwoMachinesTitle();
13457     firstMove = TRUE;
13458     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13459         onmove = &first;
13460     } else {
13461         onmove = &second;
13462     }
13463     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13464     SendToProgram(first.computerString, &first);
13465     if (first.sendName) {
13466       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13467       SendToProgram(buf, &first);
13468     }
13469     SendToProgram(second.computerString, &second);
13470     if (second.sendName) {
13471       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13472       SendToProgram(buf, &second);
13473     }
13474
13475     ResetClocks();
13476     if (!first.sendTime || !second.sendTime) {
13477         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13478         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13479     }
13480     if (onmove->sendTime) {
13481       if (onmove->useColors) {
13482         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13483       }
13484       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13485     }
13486     if (onmove->useColors) {
13487       SendToProgram(onmove->twoMachinesColor, onmove);
13488     }
13489     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13490 //    SendToProgram("go\n", onmove);
13491     onmove->maybeThinking = TRUE;
13492     SetMachineThinkingEnables();
13493
13494     StartClocks();
13495
13496     if(bookHit) { // [HGM] book: simulate book reply
13497         static char bookMove[MSG_SIZ]; // a bit generous?
13498
13499         programStats.nodes = programStats.depth = programStats.time =
13500         programStats.score = programStats.got_only_move = 0;
13501         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13502
13503         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13504         strcat(bookMove, bookHit);
13505         savedMessage = bookMove; // args for deferred call
13506         savedState = onmove;
13507         ScheduleDelayedEvent(DeferredBookMove, 1);
13508     }
13509 }
13510
13511 void
13512 TrainingEvent()
13513 {
13514     if (gameMode == Training) {
13515       SetTrainingModeOff();
13516       gameMode = PlayFromGameFile;
13517       DisplayMessage("", _("Training mode off"));
13518     } else {
13519       gameMode = Training;
13520       animateTraining = appData.animate;
13521
13522       /* make sure we are not already at the end of the game */
13523       if (currentMove < forwardMostMove) {
13524         SetTrainingModeOn();
13525         DisplayMessage("", _("Training mode on"));
13526       } else {
13527         gameMode = PlayFromGameFile;
13528         DisplayError(_("Already at end of game"), 0);
13529       }
13530     }
13531     ModeHighlight();
13532 }
13533
13534 void
13535 IcsClientEvent()
13536 {
13537     if (!appData.icsActive) return;
13538     switch (gameMode) {
13539       case IcsPlayingWhite:
13540       case IcsPlayingBlack:
13541       case IcsObserving:
13542       case IcsIdle:
13543       case BeginningOfGame:
13544       case IcsExamining:
13545         return;
13546
13547       case EditGame:
13548         break;
13549
13550       case EditPosition:
13551         EditPositionDone(TRUE);
13552         break;
13553
13554       case AnalyzeMode:
13555       case AnalyzeFile:
13556         ExitAnalyzeMode();
13557         break;
13558
13559       default:
13560         EditGameEvent();
13561         break;
13562     }
13563
13564     gameMode = IcsIdle;
13565     ModeHighlight();
13566     return;
13567 }
13568
13569
13570 void
13571 EditGameEvent()
13572 {
13573     int i;
13574
13575     switch (gameMode) {
13576       case Training:
13577         SetTrainingModeOff();
13578         break;
13579       case MachinePlaysWhite:
13580       case MachinePlaysBlack:
13581       case BeginningOfGame:
13582         SendToProgram("force\n", &first);
13583         SetUserThinkingEnables();
13584         break;
13585       case PlayFromGameFile:
13586         (void) StopLoadGameTimer();
13587         if (gameFileFP != NULL) {
13588             gameFileFP = NULL;
13589         }
13590         break;
13591       case EditPosition:
13592         EditPositionDone(TRUE);
13593         break;
13594       case AnalyzeMode:
13595       case AnalyzeFile:
13596         ExitAnalyzeMode();
13597         SendToProgram("force\n", &first);
13598         break;
13599       case TwoMachinesPlay:
13600         GameEnds(EndOfFile, NULL, GE_PLAYER);
13601         ResurrectChessProgram();
13602         SetUserThinkingEnables();
13603         break;
13604       case EndOfGame:
13605         ResurrectChessProgram();
13606         break;
13607       case IcsPlayingBlack:
13608       case IcsPlayingWhite:
13609         DisplayError(_("Warning: You are still playing a game"), 0);
13610         break;
13611       case IcsObserving:
13612         DisplayError(_("Warning: You are still observing a game"), 0);
13613         break;
13614       case IcsExamining:
13615         DisplayError(_("Warning: You are still examining a game"), 0);
13616         break;
13617       case IcsIdle:
13618         break;
13619       case EditGame:
13620       default:
13621         return;
13622     }
13623
13624     pausing = FALSE;
13625     StopClocks();
13626     first.offeredDraw = second.offeredDraw = 0;
13627
13628     if (gameMode == PlayFromGameFile) {
13629         whiteTimeRemaining = timeRemaining[0][currentMove];
13630         blackTimeRemaining = timeRemaining[1][currentMove];
13631         DisplayTitle("");
13632     }
13633
13634     if (gameMode == MachinePlaysWhite ||
13635         gameMode == MachinePlaysBlack ||
13636         gameMode == TwoMachinesPlay ||
13637         gameMode == EndOfGame) {
13638         i = forwardMostMove;
13639         while (i > currentMove) {
13640             SendToProgram("undo\n", &first);
13641             i--;
13642         }
13643         if(!adjustedClock) {
13644         whiteTimeRemaining = timeRemaining[0][currentMove];
13645         blackTimeRemaining = timeRemaining[1][currentMove];
13646         DisplayBothClocks();
13647         }
13648         if (whiteFlag || blackFlag) {
13649             whiteFlag = blackFlag = 0;
13650         }
13651         DisplayTitle("");
13652     }
13653
13654     gameMode = EditGame;
13655     ModeHighlight();
13656     SetGameInfo();
13657 }
13658
13659
13660 void
13661 EditPositionEvent()
13662 {
13663     if (gameMode == EditPosition) {
13664         EditGameEvent();
13665         return;
13666     }
13667
13668     EditGameEvent();
13669     if (gameMode != EditGame) return;
13670
13671     gameMode = EditPosition;
13672     ModeHighlight();
13673     SetGameInfo();
13674     if (currentMove > 0)
13675       CopyBoard(boards[0], boards[currentMove]);
13676
13677     blackPlaysFirst = !WhiteOnMove(currentMove);
13678     ResetClocks();
13679     currentMove = forwardMostMove = backwardMostMove = 0;
13680     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13681     DisplayMove(-1);
13682 }
13683
13684 void
13685 ExitAnalyzeMode()
13686 {
13687     /* [DM] icsEngineAnalyze - possible call from other functions */
13688     if (appData.icsEngineAnalyze) {
13689         appData.icsEngineAnalyze = FALSE;
13690
13691         DisplayMessage("",_("Close ICS engine analyze..."));
13692     }
13693     if (first.analysisSupport && first.analyzing) {
13694       SendToProgram("exit\n", &first);
13695       first.analyzing = FALSE;
13696     }
13697     thinkOutput[0] = NULLCHAR;
13698 }
13699
13700 void
13701 EditPositionDone(Boolean fakeRights)
13702 {
13703     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13704
13705     startedFromSetupPosition = TRUE;
13706     InitChessProgram(&first, FALSE);
13707     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13708       boards[0][EP_STATUS] = EP_NONE;
13709       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13710     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13711         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13712         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13713       } else boards[0][CASTLING][2] = NoRights;
13714     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13715         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13716         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13717       } else boards[0][CASTLING][5] = NoRights;
13718     }
13719     SendToProgram("force\n", &first);
13720     if (blackPlaysFirst) {
13721         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13722         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13723         currentMove = forwardMostMove = backwardMostMove = 1;
13724         CopyBoard(boards[1], boards[0]);
13725     } else {
13726         currentMove = forwardMostMove = backwardMostMove = 0;
13727     }
13728     SendBoard(&first, forwardMostMove);
13729     if (appData.debugMode) {
13730         fprintf(debugFP, "EditPosDone\n");
13731     }
13732     DisplayTitle("");
13733     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13734     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13735     gameMode = EditGame;
13736     ModeHighlight();
13737     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13738     ClearHighlights(); /* [AS] */
13739 }
13740
13741 /* Pause for `ms' milliseconds */
13742 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13743 void
13744 TimeDelay(ms)
13745      long ms;
13746 {
13747     TimeMark m1, m2;
13748
13749     GetTimeMark(&m1);
13750     do {
13751         GetTimeMark(&m2);
13752     } while (SubtractTimeMarks(&m2, &m1) < ms);
13753 }
13754
13755 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13756 void
13757 SendMultiLineToICS(buf)
13758      char *buf;
13759 {
13760     char temp[MSG_SIZ+1], *p;
13761     int len;
13762
13763     len = strlen(buf);
13764     if (len > MSG_SIZ)
13765       len = MSG_SIZ;
13766
13767     strncpy(temp, buf, len);
13768     temp[len] = 0;
13769
13770     p = temp;
13771     while (*p) {
13772         if (*p == '\n' || *p == '\r')
13773           *p = ' ';
13774         ++p;
13775     }
13776
13777     strcat(temp, "\n");
13778     SendToICS(temp);
13779     SendToPlayer(temp, strlen(temp));
13780 }
13781
13782 void
13783 SetWhiteToPlayEvent()
13784 {
13785     if (gameMode == EditPosition) {
13786         blackPlaysFirst = FALSE;
13787         DisplayBothClocks();    /* works because currentMove is 0 */
13788     } else if (gameMode == IcsExamining) {
13789         SendToICS(ics_prefix);
13790         SendToICS("tomove white\n");
13791     }
13792 }
13793
13794 void
13795 SetBlackToPlayEvent()
13796 {
13797     if (gameMode == EditPosition) {
13798         blackPlaysFirst = TRUE;
13799         currentMove = 1;        /* kludge */
13800         DisplayBothClocks();
13801         currentMove = 0;
13802     } else if (gameMode == IcsExamining) {
13803         SendToICS(ics_prefix);
13804         SendToICS("tomove black\n");
13805     }
13806 }
13807
13808 void
13809 EditPositionMenuEvent(selection, x, y)
13810      ChessSquare selection;
13811      int x, y;
13812 {
13813     char buf[MSG_SIZ];
13814     ChessSquare piece = boards[0][y][x];
13815
13816     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13817
13818     switch (selection) {
13819       case ClearBoard:
13820         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13821             SendToICS(ics_prefix);
13822             SendToICS("bsetup clear\n");
13823         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13824             SendToICS(ics_prefix);
13825             SendToICS("clearboard\n");
13826         } else {
13827             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13828                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13829                 for (y = 0; y < BOARD_HEIGHT; y++) {
13830                     if (gameMode == IcsExamining) {
13831                         if (boards[currentMove][y][x] != EmptySquare) {
13832                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13833                                     AAA + x, ONE + y);
13834                             SendToICS(buf);
13835                         }
13836                     } else {
13837                         boards[0][y][x] = p;
13838                     }
13839                 }
13840             }
13841         }
13842         if (gameMode == EditPosition) {
13843             DrawPosition(FALSE, boards[0]);
13844         }
13845         break;
13846
13847       case WhitePlay:
13848         SetWhiteToPlayEvent();
13849         break;
13850
13851       case BlackPlay:
13852         SetBlackToPlayEvent();
13853         break;
13854
13855       case EmptySquare:
13856         if (gameMode == IcsExamining) {
13857             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13858             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13859             SendToICS(buf);
13860         } else {
13861             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13862                 if(x == BOARD_LEFT-2) {
13863                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13864                     boards[0][y][1] = 0;
13865                 } else
13866                 if(x == BOARD_RGHT+1) {
13867                     if(y >= gameInfo.holdingsSize) break;
13868                     boards[0][y][BOARD_WIDTH-2] = 0;
13869                 } else break;
13870             }
13871             boards[0][y][x] = EmptySquare;
13872             DrawPosition(FALSE, boards[0]);
13873         }
13874         break;
13875
13876       case PromotePiece:
13877         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13878            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13879             selection = (ChessSquare) (PROMOTED piece);
13880         } else if(piece == EmptySquare) selection = WhiteSilver;
13881         else selection = (ChessSquare)((int)piece - 1);
13882         goto defaultlabel;
13883
13884       case DemotePiece:
13885         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13886            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13887             selection = (ChessSquare) (DEMOTED piece);
13888         } else if(piece == EmptySquare) selection = BlackSilver;
13889         else selection = (ChessSquare)((int)piece + 1);
13890         goto defaultlabel;
13891
13892       case WhiteQueen:
13893       case BlackQueen:
13894         if(gameInfo.variant == VariantShatranj ||
13895            gameInfo.variant == VariantXiangqi  ||
13896            gameInfo.variant == VariantCourier  ||
13897            gameInfo.variant == VariantMakruk     )
13898             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13899         goto defaultlabel;
13900
13901       case WhiteKing:
13902       case BlackKing:
13903         if(gameInfo.variant == VariantXiangqi)
13904             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13905         if(gameInfo.variant == VariantKnightmate)
13906             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13907       default:
13908         defaultlabel:
13909         if (gameMode == IcsExamining) {
13910             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13911             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13912                      PieceToChar(selection), AAA + x, ONE + y);
13913             SendToICS(buf);
13914         } else {
13915             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13916                 int n;
13917                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13918                     n = PieceToNumber(selection - BlackPawn);
13919                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13920                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13921                     boards[0][BOARD_HEIGHT-1-n][1]++;
13922                 } else
13923                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13924                     n = PieceToNumber(selection);
13925                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13926                     boards[0][n][BOARD_WIDTH-1] = selection;
13927                     boards[0][n][BOARD_WIDTH-2]++;
13928                 }
13929             } else
13930             boards[0][y][x] = selection;
13931             DrawPosition(TRUE, boards[0]);
13932         }
13933         break;
13934     }
13935 }
13936
13937
13938 void
13939 DropMenuEvent(selection, x, y)
13940      ChessSquare selection;
13941      int x, y;
13942 {
13943     ChessMove moveType;
13944
13945     switch (gameMode) {
13946       case IcsPlayingWhite:
13947       case MachinePlaysBlack:
13948         if (!WhiteOnMove(currentMove)) {
13949             DisplayMoveError(_("It is Black's turn"));
13950             return;
13951         }
13952         moveType = WhiteDrop;
13953         break;
13954       case IcsPlayingBlack:
13955       case MachinePlaysWhite:
13956         if (WhiteOnMove(currentMove)) {
13957             DisplayMoveError(_("It is White's turn"));
13958             return;
13959         }
13960         moveType = BlackDrop;
13961         break;
13962       case EditGame:
13963         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13964         break;
13965       default:
13966         return;
13967     }
13968
13969     if (moveType == BlackDrop && selection < BlackPawn) {
13970       selection = (ChessSquare) ((int) selection
13971                                  + (int) BlackPawn - (int) WhitePawn);
13972     }
13973     if (boards[currentMove][y][x] != EmptySquare) {
13974         DisplayMoveError(_("That square is occupied"));
13975         return;
13976     }
13977
13978     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13979 }
13980
13981 void
13982 AcceptEvent()
13983 {
13984     /* Accept a pending offer of any kind from opponent */
13985
13986     if (appData.icsActive) {
13987         SendToICS(ics_prefix);
13988         SendToICS("accept\n");
13989     } else if (cmailMsgLoaded) {
13990         if (currentMove == cmailOldMove &&
13991             commentList[cmailOldMove] != NULL &&
13992             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13993                    "Black offers a draw" : "White offers a draw")) {
13994             TruncateGame();
13995             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13996             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13997         } else {
13998             DisplayError(_("There is no pending offer on this move"), 0);
13999             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14000         }
14001     } else {
14002         /* Not used for offers from chess program */
14003     }
14004 }
14005
14006 void
14007 DeclineEvent()
14008 {
14009     /* Decline a pending offer of any kind from opponent */
14010
14011     if (appData.icsActive) {
14012         SendToICS(ics_prefix);
14013         SendToICS("decline\n");
14014     } else if (cmailMsgLoaded) {
14015         if (currentMove == cmailOldMove &&
14016             commentList[cmailOldMove] != NULL &&
14017             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14018                    "Black offers a draw" : "White offers a draw")) {
14019 #ifdef NOTDEF
14020             AppendComment(cmailOldMove, "Draw declined", TRUE);
14021             DisplayComment(cmailOldMove - 1, "Draw declined");
14022 #endif /*NOTDEF*/
14023         } else {
14024             DisplayError(_("There is no pending offer on this move"), 0);
14025         }
14026     } else {
14027         /* Not used for offers from chess program */
14028     }
14029 }
14030
14031 void
14032 RematchEvent()
14033 {
14034     /* Issue ICS rematch command */
14035     if (appData.icsActive) {
14036         SendToICS(ics_prefix);
14037         SendToICS("rematch\n");
14038     }
14039 }
14040
14041 void
14042 CallFlagEvent()
14043 {
14044     /* Call your opponent's flag (claim a win on time) */
14045     if (appData.icsActive) {
14046         SendToICS(ics_prefix);
14047         SendToICS("flag\n");
14048     } else {
14049         switch (gameMode) {
14050           default:
14051             return;
14052           case MachinePlaysWhite:
14053             if (whiteFlag) {
14054                 if (blackFlag)
14055                   GameEnds(GameIsDrawn, "Both players ran out of time",
14056                            GE_PLAYER);
14057                 else
14058                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14059             } else {
14060                 DisplayError(_("Your opponent is not out of time"), 0);
14061             }
14062             break;
14063           case MachinePlaysBlack:
14064             if (blackFlag) {
14065                 if (whiteFlag)
14066                   GameEnds(GameIsDrawn, "Both players ran out of time",
14067                            GE_PLAYER);
14068                 else
14069                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14070             } else {
14071                 DisplayError(_("Your opponent is not out of time"), 0);
14072             }
14073             break;
14074         }
14075     }
14076 }
14077
14078 void
14079 ClockClick(int which)
14080 {       // [HGM] code moved to back-end from winboard.c
14081         if(which) { // black clock
14082           if (gameMode == EditPosition || gameMode == IcsExamining) {
14083             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14084             SetBlackToPlayEvent();
14085           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14086           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14087           } else if (shiftKey) {
14088             AdjustClock(which, -1);
14089           } else if (gameMode == IcsPlayingWhite ||
14090                      gameMode == MachinePlaysBlack) {
14091             CallFlagEvent();
14092           }
14093         } else { // white clock
14094           if (gameMode == EditPosition || gameMode == IcsExamining) {
14095             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14096             SetWhiteToPlayEvent();
14097           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14098           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14099           } else if (shiftKey) {
14100             AdjustClock(which, -1);
14101           } else if (gameMode == IcsPlayingBlack ||
14102                    gameMode == MachinePlaysWhite) {
14103             CallFlagEvent();
14104           }
14105         }
14106 }
14107
14108 void
14109 DrawEvent()
14110 {
14111     /* Offer draw or accept pending draw offer from opponent */
14112
14113     if (appData.icsActive) {
14114         /* Note: tournament rules require draw offers to be
14115            made after you make your move but before you punch
14116            your clock.  Currently ICS doesn't let you do that;
14117            instead, you immediately punch your clock after making
14118            a move, but you can offer a draw at any time. */
14119
14120         SendToICS(ics_prefix);
14121         SendToICS("draw\n");
14122         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14123     } else if (cmailMsgLoaded) {
14124         if (currentMove == cmailOldMove &&
14125             commentList[cmailOldMove] != NULL &&
14126             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14127                    "Black offers a draw" : "White offers a draw")) {
14128             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14130         } else if (currentMove == cmailOldMove + 1) {
14131             char *offer = WhiteOnMove(cmailOldMove) ?
14132               "White offers a draw" : "Black offers a draw";
14133             AppendComment(currentMove, offer, TRUE);
14134             DisplayComment(currentMove - 1, offer);
14135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14136         } else {
14137             DisplayError(_("You must make your move before offering a draw"), 0);
14138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14139         }
14140     } else if (first.offeredDraw) {
14141         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14142     } else {
14143         if (first.sendDrawOffers) {
14144             SendToProgram("draw\n", &first);
14145             userOfferedDraw = TRUE;
14146         }
14147     }
14148 }
14149
14150 void
14151 AdjournEvent()
14152 {
14153     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14154
14155     if (appData.icsActive) {
14156         SendToICS(ics_prefix);
14157         SendToICS("adjourn\n");
14158     } else {
14159         /* Currently GNU Chess doesn't offer or accept Adjourns */
14160     }
14161 }
14162
14163
14164 void
14165 AbortEvent()
14166 {
14167     /* Offer Abort or accept pending Abort offer from opponent */
14168
14169     if (appData.icsActive) {
14170         SendToICS(ics_prefix);
14171         SendToICS("abort\n");
14172     } else {
14173         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14174     }
14175 }
14176
14177 void
14178 ResignEvent()
14179 {
14180     /* Resign.  You can do this even if it's not your turn. */
14181
14182     if (appData.icsActive) {
14183         SendToICS(ics_prefix);
14184         SendToICS("resign\n");
14185     } else {
14186         switch (gameMode) {
14187           case MachinePlaysWhite:
14188             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14189             break;
14190           case MachinePlaysBlack:
14191             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14192             break;
14193           case EditGame:
14194             if (cmailMsgLoaded) {
14195                 TruncateGame();
14196                 if (WhiteOnMove(cmailOldMove)) {
14197                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14198                 } else {
14199                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14200                 }
14201                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14202             }
14203             break;
14204           default:
14205             break;
14206         }
14207     }
14208 }
14209
14210
14211 void
14212 StopObservingEvent()
14213 {
14214     /* Stop observing current games */
14215     SendToICS(ics_prefix);
14216     SendToICS("unobserve\n");
14217 }
14218
14219 void
14220 StopExaminingEvent()
14221 {
14222     /* Stop observing current game */
14223     SendToICS(ics_prefix);
14224     SendToICS("unexamine\n");
14225 }
14226
14227 void
14228 ForwardInner(target)
14229      int target;
14230 {
14231     int limit;
14232
14233     if (appData.debugMode)
14234         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14235                 target, currentMove, forwardMostMove);
14236
14237     if (gameMode == EditPosition)
14238       return;
14239
14240     MarkTargetSquares(1);
14241
14242     if (gameMode == PlayFromGameFile && !pausing)
14243       PauseEvent();
14244
14245     if (gameMode == IcsExamining && pausing)
14246       limit = pauseExamForwardMostMove;
14247     else
14248       limit = forwardMostMove;
14249
14250     if (target > limit) target = limit;
14251
14252     if (target > 0 && moveList[target - 1][0]) {
14253         int fromX, fromY, toX, toY;
14254         toX = moveList[target - 1][2] - AAA;
14255         toY = moveList[target - 1][3] - ONE;
14256         if (moveList[target - 1][1] == '@') {
14257             if (appData.highlightLastMove) {
14258                 SetHighlights(-1, -1, toX, toY);
14259             }
14260         } else {
14261             fromX = moveList[target - 1][0] - AAA;
14262             fromY = moveList[target - 1][1] - ONE;
14263             if (target == currentMove + 1) {
14264                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14265             }
14266             if (appData.highlightLastMove) {
14267                 SetHighlights(fromX, fromY, toX, toY);
14268             }
14269         }
14270     }
14271     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14272         gameMode == Training || gameMode == PlayFromGameFile ||
14273         gameMode == AnalyzeFile) {
14274         while (currentMove < target) {
14275             SendMoveToProgram(currentMove++, &first);
14276         }
14277     } else {
14278         currentMove = target;
14279     }
14280
14281     if (gameMode == EditGame || gameMode == EndOfGame) {
14282         whiteTimeRemaining = timeRemaining[0][currentMove];
14283         blackTimeRemaining = timeRemaining[1][currentMove];
14284     }
14285     DisplayBothClocks();
14286     DisplayMove(currentMove - 1);
14287     DrawPosition(FALSE, boards[currentMove]);
14288     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14289     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14290         DisplayComment(currentMove - 1, commentList[currentMove]);
14291     }
14292 }
14293
14294
14295 void
14296 ForwardEvent()
14297 {
14298     if (gameMode == IcsExamining && !pausing) {
14299         SendToICS(ics_prefix);
14300         SendToICS("forward\n");
14301     } else {
14302         ForwardInner(currentMove + 1);
14303     }
14304 }
14305
14306 void
14307 ToEndEvent()
14308 {
14309     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14310         /* to optimze, we temporarily turn off analysis mode while we feed
14311          * the remaining moves to the engine. Otherwise we get analysis output
14312          * after each move.
14313          */
14314         if (first.analysisSupport) {
14315           SendToProgram("exit\nforce\n", &first);
14316           first.analyzing = FALSE;
14317         }
14318     }
14319
14320     if (gameMode == IcsExamining && !pausing) {
14321         SendToICS(ics_prefix);
14322         SendToICS("forward 999999\n");
14323     } else {
14324         ForwardInner(forwardMostMove);
14325     }
14326
14327     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14328         /* we have fed all the moves, so reactivate analysis mode */
14329         SendToProgram("analyze\n", &first);
14330         first.analyzing = TRUE;
14331         /*first.maybeThinking = TRUE;*/
14332         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14333     }
14334 }
14335
14336 void
14337 BackwardInner(target)
14338      int target;
14339 {
14340     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14341
14342     if (appData.debugMode)
14343         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14344                 target, currentMove, forwardMostMove);
14345
14346     if (gameMode == EditPosition) return;
14347     MarkTargetSquares(1);
14348     if (currentMove <= backwardMostMove) {
14349         ClearHighlights();
14350         DrawPosition(full_redraw, boards[currentMove]);
14351         return;
14352     }
14353     if (gameMode == PlayFromGameFile && !pausing)
14354       PauseEvent();
14355
14356     if (moveList[target][0]) {
14357         int fromX, fromY, toX, toY;
14358         toX = moveList[target][2] - AAA;
14359         toY = moveList[target][3] - ONE;
14360         if (moveList[target][1] == '@') {
14361             if (appData.highlightLastMove) {
14362                 SetHighlights(-1, -1, toX, toY);
14363             }
14364         } else {
14365             fromX = moveList[target][0] - AAA;
14366             fromY = moveList[target][1] - ONE;
14367             if (target == currentMove - 1) {
14368                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14369             }
14370             if (appData.highlightLastMove) {
14371                 SetHighlights(fromX, fromY, toX, toY);
14372             }
14373         }
14374     }
14375     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14376         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14377         while (currentMove > target) {
14378             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14379                 // null move cannot be undone. Reload program with move history before it.
14380                 int i;
14381                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14382                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14383                 }
14384                 SendBoard(&first, i); 
14385                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14386                 break;
14387             }
14388             SendToProgram("undo\n", &first);
14389             currentMove--;
14390         }
14391     } else {
14392         currentMove = target;
14393     }
14394
14395     if (gameMode == EditGame || gameMode == EndOfGame) {
14396         whiteTimeRemaining = timeRemaining[0][currentMove];
14397         blackTimeRemaining = timeRemaining[1][currentMove];
14398     }
14399     DisplayBothClocks();
14400     DisplayMove(currentMove - 1);
14401     DrawPosition(full_redraw, boards[currentMove]);
14402     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14403     // [HGM] PV info: routine tests if comment empty
14404     DisplayComment(currentMove - 1, commentList[currentMove]);
14405 }
14406
14407 void
14408 BackwardEvent()
14409 {
14410     if (gameMode == IcsExamining && !pausing) {
14411         SendToICS(ics_prefix);
14412         SendToICS("backward\n");
14413     } else {
14414         BackwardInner(currentMove - 1);
14415     }
14416 }
14417
14418 void
14419 ToStartEvent()
14420 {
14421     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14422         /* to optimize, we temporarily turn off analysis mode while we undo
14423          * all the moves. Otherwise we get analysis output after each undo.
14424          */
14425         if (first.analysisSupport) {
14426           SendToProgram("exit\nforce\n", &first);
14427           first.analyzing = FALSE;
14428         }
14429     }
14430
14431     if (gameMode == IcsExamining && !pausing) {
14432         SendToICS(ics_prefix);
14433         SendToICS("backward 999999\n");
14434     } else {
14435         BackwardInner(backwardMostMove);
14436     }
14437
14438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14439         /* we have fed all the moves, so reactivate analysis mode */
14440         SendToProgram("analyze\n", &first);
14441         first.analyzing = TRUE;
14442         /*first.maybeThinking = TRUE;*/
14443         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14444     }
14445 }
14446
14447 void
14448 ToNrEvent(int to)
14449 {
14450   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14451   if (to >= forwardMostMove) to = forwardMostMove;
14452   if (to <= backwardMostMove) to = backwardMostMove;
14453   if (to < currentMove) {
14454     BackwardInner(to);
14455   } else {
14456     ForwardInner(to);
14457   }
14458 }
14459
14460 void
14461 RevertEvent(Boolean annotate)
14462 {
14463     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14464         return;
14465     }
14466     if (gameMode != IcsExamining) {
14467         DisplayError(_("You are not examining a game"), 0);
14468         return;
14469     }
14470     if (pausing) {
14471         DisplayError(_("You can't revert while pausing"), 0);
14472         return;
14473     }
14474     SendToICS(ics_prefix);
14475     SendToICS("revert\n");
14476 }
14477
14478 void
14479 RetractMoveEvent()
14480 {
14481     switch (gameMode) {
14482       case MachinePlaysWhite:
14483       case MachinePlaysBlack:
14484         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14485             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14486             return;
14487         }
14488         if (forwardMostMove < 2) return;
14489         currentMove = forwardMostMove = forwardMostMove - 2;
14490         whiteTimeRemaining = timeRemaining[0][currentMove];
14491         blackTimeRemaining = timeRemaining[1][currentMove];
14492         DisplayBothClocks();
14493         DisplayMove(currentMove - 1);
14494         ClearHighlights();/*!! could figure this out*/
14495         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14496         SendToProgram("remove\n", &first);
14497         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14498         break;
14499
14500       case BeginningOfGame:
14501       default:
14502         break;
14503
14504       case IcsPlayingWhite:
14505       case IcsPlayingBlack:
14506         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14507             SendToICS(ics_prefix);
14508             SendToICS("takeback 2\n");
14509         } else {
14510             SendToICS(ics_prefix);
14511             SendToICS("takeback 1\n");
14512         }
14513         break;
14514     }
14515 }
14516
14517 void
14518 MoveNowEvent()
14519 {
14520     ChessProgramState *cps;
14521
14522     switch (gameMode) {
14523       case MachinePlaysWhite:
14524         if (!WhiteOnMove(forwardMostMove)) {
14525             DisplayError(_("It is your turn"), 0);
14526             return;
14527         }
14528         cps = &first;
14529         break;
14530       case MachinePlaysBlack:
14531         if (WhiteOnMove(forwardMostMove)) {
14532             DisplayError(_("It is your turn"), 0);
14533             return;
14534         }
14535         cps = &first;
14536         break;
14537       case TwoMachinesPlay:
14538         if (WhiteOnMove(forwardMostMove) ==
14539             (first.twoMachinesColor[0] == 'w')) {
14540             cps = &first;
14541         } else {
14542             cps = &second;
14543         }
14544         break;
14545       case BeginningOfGame:
14546       default:
14547         return;
14548     }
14549     SendToProgram("?\n", cps);
14550 }
14551
14552 void
14553 TruncateGameEvent()
14554 {
14555     EditGameEvent();
14556     if (gameMode != EditGame) return;
14557     TruncateGame();
14558 }
14559
14560 void
14561 TruncateGame()
14562 {
14563     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14564     if (forwardMostMove > currentMove) {
14565         if (gameInfo.resultDetails != NULL) {
14566             free(gameInfo.resultDetails);
14567             gameInfo.resultDetails = NULL;
14568             gameInfo.result = GameUnfinished;
14569         }
14570         forwardMostMove = currentMove;
14571         HistorySet(parseList, backwardMostMove, forwardMostMove,
14572                    currentMove-1);
14573     }
14574 }
14575
14576 void
14577 HintEvent()
14578 {
14579     if (appData.noChessProgram) return;
14580     switch (gameMode) {
14581       case MachinePlaysWhite:
14582         if (WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       case BeginningOfGame:
14588       case MachinePlaysBlack:
14589         if (!WhiteOnMove(forwardMostMove)) {
14590             DisplayError(_("Wait until your turn"), 0);
14591             return;
14592         }
14593         break;
14594       default:
14595         DisplayError(_("No hint available"), 0);
14596         return;
14597     }
14598     SendToProgram("hint\n", &first);
14599     hintRequested = TRUE;
14600 }
14601
14602 void
14603 BookEvent()
14604 {
14605     if (appData.noChessProgram) return;
14606     switch (gameMode) {
14607       case MachinePlaysWhite:
14608         if (WhiteOnMove(forwardMostMove)) {
14609             DisplayError(_("Wait until your turn"), 0);
14610             return;
14611         }
14612         break;
14613       case BeginningOfGame:
14614       case MachinePlaysBlack:
14615         if (!WhiteOnMove(forwardMostMove)) {
14616             DisplayError(_("Wait until your turn"), 0);
14617             return;
14618         }
14619         break;
14620       case EditPosition:
14621         EditPositionDone(TRUE);
14622         break;
14623       case TwoMachinesPlay:
14624         return;
14625       default:
14626         break;
14627     }
14628     SendToProgram("bk\n", &first);
14629     bookOutput[0] = NULLCHAR;
14630     bookRequested = TRUE;
14631 }
14632
14633 void
14634 AboutGameEvent()
14635 {
14636     char *tags = PGNTags(&gameInfo);
14637     TagsPopUp(tags, CmailMsg());
14638     free(tags);
14639 }
14640
14641 /* end button procedures */
14642
14643 void
14644 PrintPosition(fp, move)
14645      FILE *fp;
14646      int move;
14647 {
14648     int i, j;
14649
14650     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14651         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14652             char c = PieceToChar(boards[move][i][j]);
14653             fputc(c == 'x' ? '.' : c, fp);
14654             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14655         }
14656     }
14657     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14658       fprintf(fp, "white to play\n");
14659     else
14660       fprintf(fp, "black to play\n");
14661 }
14662
14663 void
14664 PrintOpponents(fp)
14665      FILE *fp;
14666 {
14667     if (gameInfo.white != NULL) {
14668         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14669     } else {
14670         fprintf(fp, "\n");
14671     }
14672 }
14673
14674 /* Find last component of program's own name, using some heuristics */
14675 void
14676 TidyProgramName(prog, host, buf)
14677      char *prog, *host, buf[MSG_SIZ];
14678 {
14679     char *p, *q;
14680     int local = (strcmp(host, "localhost") == 0);
14681     while (!local && (p = strchr(prog, ';')) != NULL) {
14682         p++;
14683         while (*p == ' ') p++;
14684         prog = p;
14685     }
14686     if (*prog == '"' || *prog == '\'') {
14687         q = strchr(prog + 1, *prog);
14688     } else {
14689         q = strchr(prog, ' ');
14690     }
14691     if (q == NULL) q = prog + strlen(prog);
14692     p = q;
14693     while (p >= prog && *p != '/' && *p != '\\') p--;
14694     p++;
14695     if(p == prog && *p == '"') p++;
14696     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14697     memcpy(buf, p, q - p);
14698     buf[q - p] = NULLCHAR;
14699     if (!local) {
14700         strcat(buf, "@");
14701         strcat(buf, host);
14702     }
14703 }
14704
14705 char *
14706 TimeControlTagValue()
14707 {
14708     char buf[MSG_SIZ];
14709     if (!appData.clockMode) {
14710       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14711     } else if (movesPerSession > 0) {
14712       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14713     } else if (timeIncrement == 0) {
14714       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14715     } else {
14716       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14717     }
14718     return StrSave(buf);
14719 }
14720
14721 void
14722 SetGameInfo()
14723 {
14724     /* This routine is used only for certain modes */
14725     VariantClass v = gameInfo.variant;
14726     ChessMove r = GameUnfinished;
14727     char *p = NULL;
14728
14729     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14730         r = gameInfo.result;
14731         p = gameInfo.resultDetails;
14732         gameInfo.resultDetails = NULL;
14733     }
14734     ClearGameInfo(&gameInfo);
14735     gameInfo.variant = v;
14736
14737     switch (gameMode) {
14738       case MachinePlaysWhite:
14739         gameInfo.event = StrSave( appData.pgnEventHeader );
14740         gameInfo.site = StrSave(HostName());
14741         gameInfo.date = PGNDate();
14742         gameInfo.round = StrSave("-");
14743         gameInfo.white = StrSave(first.tidy);
14744         gameInfo.black = StrSave(UserName());
14745         gameInfo.timeControl = TimeControlTagValue();
14746         break;
14747
14748       case MachinePlaysBlack:
14749         gameInfo.event = StrSave( appData.pgnEventHeader );
14750         gameInfo.site = StrSave(HostName());
14751         gameInfo.date = PGNDate();
14752         gameInfo.round = StrSave("-");
14753         gameInfo.white = StrSave(UserName());
14754         gameInfo.black = StrSave(first.tidy);
14755         gameInfo.timeControl = TimeControlTagValue();
14756         break;
14757
14758       case TwoMachinesPlay:
14759         gameInfo.event = StrSave( appData.pgnEventHeader );
14760         gameInfo.site = StrSave(HostName());
14761         gameInfo.date = PGNDate();
14762         if (roundNr > 0) {
14763             char buf[MSG_SIZ];
14764             snprintf(buf, MSG_SIZ, "%d", roundNr);
14765             gameInfo.round = StrSave(buf);
14766         } else {
14767             gameInfo.round = StrSave("-");
14768         }
14769         if (first.twoMachinesColor[0] == 'w') {
14770             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14771             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14772         } else {
14773             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14774             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14775         }
14776         gameInfo.timeControl = TimeControlTagValue();
14777         break;
14778
14779       case EditGame:
14780         gameInfo.event = StrSave("Edited game");
14781         gameInfo.site = StrSave(HostName());
14782         gameInfo.date = PGNDate();
14783         gameInfo.round = StrSave("-");
14784         gameInfo.white = StrSave("-");
14785         gameInfo.black = StrSave("-");
14786         gameInfo.result = r;
14787         gameInfo.resultDetails = p;
14788         break;
14789
14790       case EditPosition:
14791         gameInfo.event = StrSave("Edited position");
14792         gameInfo.site = StrSave(HostName());
14793         gameInfo.date = PGNDate();
14794         gameInfo.round = StrSave("-");
14795         gameInfo.white = StrSave("-");
14796         gameInfo.black = StrSave("-");
14797         break;
14798
14799       case IcsPlayingWhite:
14800       case IcsPlayingBlack:
14801       case IcsObserving:
14802       case IcsExamining:
14803         break;
14804
14805       case PlayFromGameFile:
14806         gameInfo.event = StrSave("Game from non-PGN file");
14807         gameInfo.site = StrSave(HostName());
14808         gameInfo.date = PGNDate();
14809         gameInfo.round = StrSave("-");
14810         gameInfo.white = StrSave("?");
14811         gameInfo.black = StrSave("?");
14812         break;
14813
14814       default:
14815         break;
14816     }
14817 }
14818
14819 void
14820 ReplaceComment(index, text)
14821      int index;
14822      char *text;
14823 {
14824     int len;
14825     char *p;
14826     float score;
14827
14828     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14829        pvInfoList[index-1].depth == len &&
14830        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14831        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14832     while (*text == '\n') text++;
14833     len = strlen(text);
14834     while (len > 0 && text[len - 1] == '\n') len--;
14835
14836     if (commentList[index] != NULL)
14837       free(commentList[index]);
14838
14839     if (len == 0) {
14840         commentList[index] = NULL;
14841         return;
14842     }
14843   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14844       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14845       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14846     commentList[index] = (char *) malloc(len + 2);
14847     strncpy(commentList[index], text, len);
14848     commentList[index][len] = '\n';
14849     commentList[index][len + 1] = NULLCHAR;
14850   } else {
14851     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14852     char *p;
14853     commentList[index] = (char *) malloc(len + 7);
14854     safeStrCpy(commentList[index], "{\n", 3);
14855     safeStrCpy(commentList[index]+2, text, len+1);
14856     commentList[index][len+2] = NULLCHAR;
14857     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14858     strcat(commentList[index], "\n}\n");
14859   }
14860 }
14861
14862 void
14863 CrushCRs(text)
14864      char *text;
14865 {
14866   char *p = text;
14867   char *q = text;
14868   char ch;
14869
14870   do {
14871     ch = *p++;
14872     if (ch == '\r') continue;
14873     *q++ = ch;
14874   } while (ch != '\0');
14875 }
14876
14877 void
14878 AppendComment(index, text, addBraces)
14879      int index;
14880      char *text;
14881      Boolean addBraces; // [HGM] braces: tells if we should add {}
14882 {
14883     int oldlen, len;
14884     char *old;
14885
14886 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14887     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14888
14889     CrushCRs(text);
14890     while (*text == '\n') text++;
14891     len = strlen(text);
14892     while (len > 0 && text[len - 1] == '\n') len--;
14893
14894     if (len == 0) return;
14895
14896     if (commentList[index] != NULL) {
14897       Boolean addClosingBrace = addBraces;
14898         old = commentList[index];
14899         oldlen = strlen(old);
14900         while(commentList[index][oldlen-1] ==  '\n')
14901           commentList[index][--oldlen] = NULLCHAR;
14902         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14903         safeStrCpy(commentList[index], old, oldlen + len + 6);
14904         free(old);
14905         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14906         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14907           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14908           while (*text == '\n') { text++; len--; }
14909           commentList[index][--oldlen] = NULLCHAR;
14910       }
14911         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14912         else          strcat(commentList[index], "\n");
14913         strcat(commentList[index], text);
14914         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14915         else          strcat(commentList[index], "\n");
14916     } else {
14917         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14918         if(addBraces)
14919           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14920         else commentList[index][0] = NULLCHAR;
14921         strcat(commentList[index], text);
14922         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14923         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14924     }
14925 }
14926
14927 static char * FindStr( char * text, char * sub_text )
14928 {
14929     char * result = strstr( text, sub_text );
14930
14931     if( result != NULL ) {
14932         result += strlen( sub_text );
14933     }
14934
14935     return result;
14936 }
14937
14938 /* [AS] Try to extract PV info from PGN comment */
14939 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14940 char *GetInfoFromComment( int index, char * text )
14941 {
14942     char * sep = text, *p;
14943
14944     if( text != NULL && index > 0 ) {
14945         int score = 0;
14946         int depth = 0;
14947         int time = -1, sec = 0, deci;
14948         char * s_eval = FindStr( text, "[%eval " );
14949         char * s_emt = FindStr( text, "[%emt " );
14950
14951         if( s_eval != NULL || s_emt != NULL ) {
14952             /* New style */
14953             char delim;
14954
14955             if( s_eval != NULL ) {
14956                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14957                     return text;
14958                 }
14959
14960                 if( delim != ']' ) {
14961                     return text;
14962                 }
14963             }
14964
14965             if( s_emt != NULL ) {
14966             }
14967                 return text;
14968         }
14969         else {
14970             /* We expect something like: [+|-]nnn.nn/dd */
14971             int score_lo = 0;
14972
14973             if(*text != '{') return text; // [HGM] braces: must be normal comment
14974
14975             sep = strchr( text, '/' );
14976             if( sep == NULL || sep < (text+4) ) {
14977                 return text;
14978             }
14979
14980             p = text;
14981             if(p[1] == '(') { // comment starts with PV
14982                p = strchr(p, ')'); // locate end of PV
14983                if(p == NULL || sep < p+5) return text;
14984                // at this point we have something like "{(.*) +0.23/6 ..."
14985                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14986                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14987                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14988             }
14989             time = -1; sec = -1; deci = -1;
14990             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14991                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14992                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14993                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14994                 return text;
14995             }
14996
14997             if( score_lo < 0 || score_lo >= 100 ) {
14998                 return text;
14999             }
15000
15001             if(sec >= 0) time = 600*time + 10*sec; else
15002             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15003
15004             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15005
15006             /* [HGM] PV time: now locate end of PV info */
15007             while( *++sep >= '0' && *sep <= '9'); // strip depth
15008             if(time >= 0)
15009             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15010             if(sec >= 0)
15011             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15012             if(deci >= 0)
15013             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15014             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15015         }
15016
15017         if( depth <= 0 ) {
15018             return text;
15019         }
15020
15021         if( time < 0 ) {
15022             time = -1;
15023         }
15024
15025         pvInfoList[index-1].depth = depth;
15026         pvInfoList[index-1].score = score;
15027         pvInfoList[index-1].time  = 10*time; // centi-sec
15028         if(*sep == '}') *sep = 0; else *--sep = '{';
15029         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15030     }
15031     return sep;
15032 }
15033
15034 void
15035 SendToProgram(message, cps)
15036      char *message;
15037      ChessProgramState *cps;
15038 {
15039     int count, outCount, error;
15040     char buf[MSG_SIZ];
15041
15042     if (cps->pr == NoProc) return;
15043     Attention(cps);
15044
15045     if (appData.debugMode) {
15046         TimeMark now;
15047         GetTimeMark(&now);
15048         fprintf(debugFP, "%ld >%-6s: %s",
15049                 SubtractTimeMarks(&now, &programStartTime),
15050                 cps->which, message);
15051     }
15052
15053     count = strlen(message);
15054     outCount = OutputToProcess(cps->pr, message, count, &error);
15055     if (outCount < count && !exiting
15056                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15057       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15058       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15060             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15061                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15062                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15063                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15064             } else {
15065                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15066                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15067                 gameInfo.result = res;
15068             }
15069             gameInfo.resultDetails = StrSave(buf);
15070         }
15071         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15072         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15073     }
15074 }
15075
15076 void
15077 ReceiveFromProgram(isr, closure, message, count, error)
15078      InputSourceRef isr;
15079      VOIDSTAR closure;
15080      char *message;
15081      int count;
15082      int error;
15083 {
15084     char *end_str;
15085     char buf[MSG_SIZ];
15086     ChessProgramState *cps = (ChessProgramState *)closure;
15087
15088     if (isr != cps->isr) return; /* Killed intentionally */
15089     if (count <= 0) {
15090         if (count == 0) {
15091             RemoveInputSource(cps->isr);
15092             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15093             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15094                     _(cps->which), cps->program);
15095         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15096                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15097                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15098                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15099                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15100                 } else {
15101                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15102                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15103                     gameInfo.result = res;
15104                 }
15105                 gameInfo.resultDetails = StrSave(buf);
15106             }
15107             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15108             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15109         } else {
15110             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15111                     _(cps->which), cps->program);
15112             RemoveInputSource(cps->isr);
15113
15114             /* [AS] Program is misbehaving badly... kill it */
15115             if( count == -2 ) {
15116                 DestroyChildProcess( cps->pr, 9 );
15117                 cps->pr = NoProc;
15118             }
15119
15120             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15121         }
15122         return;
15123     }
15124
15125     if ((end_str = strchr(message, '\r')) != NULL)
15126       *end_str = NULLCHAR;
15127     if ((end_str = strchr(message, '\n')) != NULL)
15128       *end_str = NULLCHAR;
15129
15130     if (appData.debugMode) {
15131         TimeMark now; int print = 1;
15132         char *quote = ""; char c; int i;
15133
15134         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15135                 char start = message[0];
15136                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15137                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15138                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15139                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15140                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15141                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15142                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15143                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15144                    sscanf(message, "hint: %c", &c)!=1 && 
15145                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15146                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15147                     print = (appData.engineComments >= 2);
15148                 }
15149                 message[0] = start; // restore original message
15150         }
15151         if(print) {
15152                 GetTimeMark(&now);
15153                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15154                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15155                         quote,
15156                         message);
15157         }
15158     }
15159
15160     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15161     if (appData.icsEngineAnalyze) {
15162         if (strstr(message, "whisper") != NULL ||
15163              strstr(message, "kibitz") != NULL ||
15164             strstr(message, "tellics") != NULL) return;
15165     }
15166
15167     HandleMachineMove(message, cps);
15168 }
15169
15170
15171 void
15172 SendTimeControl(cps, mps, tc, inc, sd, st)
15173      ChessProgramState *cps;
15174      int mps, inc, sd, st;
15175      long tc;
15176 {
15177     char buf[MSG_SIZ];
15178     int seconds;
15179
15180     if( timeControl_2 > 0 ) {
15181         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15182             tc = timeControl_2;
15183         }
15184     }
15185     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15186     inc /= cps->timeOdds;
15187     st  /= cps->timeOdds;
15188
15189     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15190
15191     if (st > 0) {
15192       /* Set exact time per move, normally using st command */
15193       if (cps->stKludge) {
15194         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15195         seconds = st % 60;
15196         if (seconds == 0) {
15197           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15198         } else {
15199           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15200         }
15201       } else {
15202         snprintf(buf, MSG_SIZ, "st %d\n", st);
15203       }
15204     } else {
15205       /* Set conventional or incremental time control, using level command */
15206       if (seconds == 0) {
15207         /* Note old gnuchess bug -- minutes:seconds used to not work.
15208            Fixed in later versions, but still avoid :seconds
15209            when seconds is 0. */
15210         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15211       } else {
15212         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15213                  seconds, inc/1000.);
15214       }
15215     }
15216     SendToProgram(buf, cps);
15217
15218     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15219     /* Orthogonally, limit search to given depth */
15220     if (sd > 0) {
15221       if (cps->sdKludge) {
15222         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15223       } else {
15224         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15225       }
15226       SendToProgram(buf, cps);
15227     }
15228
15229     if(cps->nps >= 0) { /* [HGM] nps */
15230         if(cps->supportsNPS == FALSE)
15231           cps->nps = -1; // don't use if engine explicitly says not supported!
15232         else {
15233           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15234           SendToProgram(buf, cps);
15235         }
15236     }
15237 }
15238
15239 ChessProgramState *WhitePlayer()
15240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15241 {
15242     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15243        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15244         return &second;
15245     return &first;
15246 }
15247
15248 void
15249 SendTimeRemaining(cps, machineWhite)
15250      ChessProgramState *cps;
15251      int /*boolean*/ machineWhite;
15252 {
15253     char message[MSG_SIZ];
15254     long time, otime;
15255
15256     /* Note: this routine must be called when the clocks are stopped
15257        or when they have *just* been set or switched; otherwise
15258        it will be off by the time since the current tick started.
15259     */
15260     if (machineWhite) {
15261         time = whiteTimeRemaining / 10;
15262         otime = blackTimeRemaining / 10;
15263     } else {
15264         time = blackTimeRemaining / 10;
15265         otime = whiteTimeRemaining / 10;
15266     }
15267     /* [HGM] translate opponent's time by time-odds factor */
15268     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15269     if (appData.debugMode) {
15270         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15271     }
15272
15273     if (time <= 0) time = 1;
15274     if (otime <= 0) otime = 1;
15275
15276     snprintf(message, MSG_SIZ, "time %ld\n", time);
15277     SendToProgram(message, cps);
15278
15279     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15280     SendToProgram(message, cps);
15281 }
15282
15283 int
15284 BoolFeature(p, name, loc, cps)
15285      char **p;
15286      char *name;
15287      int *loc;
15288      ChessProgramState *cps;
15289 {
15290   char buf[MSG_SIZ];
15291   int len = strlen(name);
15292   int val;
15293
15294   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15295     (*p) += len + 1;
15296     sscanf(*p, "%d", &val);
15297     *loc = (val != 0);
15298     while (**p && **p != ' ')
15299       (*p)++;
15300     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15301     SendToProgram(buf, cps);
15302     return TRUE;
15303   }
15304   return FALSE;
15305 }
15306
15307 int
15308 IntFeature(p, name, loc, cps)
15309      char **p;
15310      char *name;
15311      int *loc;
15312      ChessProgramState *cps;
15313 {
15314   char buf[MSG_SIZ];
15315   int len = strlen(name);
15316   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15317     (*p) += len + 1;
15318     sscanf(*p, "%d", loc);
15319     while (**p && **p != ' ') (*p)++;
15320     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15321     SendToProgram(buf, cps);
15322     return TRUE;
15323   }
15324   return FALSE;
15325 }
15326
15327 int
15328 StringFeature(p, name, loc, cps)
15329      char **p;
15330      char *name;
15331      char loc[];
15332      ChessProgramState *cps;
15333 {
15334   char buf[MSG_SIZ];
15335   int len = strlen(name);
15336   if (strncmp((*p), name, len) == 0
15337       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15338     (*p) += len + 2;
15339     sscanf(*p, "%[^\"]", loc);
15340     while (**p && **p != '\"') (*p)++;
15341     if (**p == '\"') (*p)++;
15342     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15343     SendToProgram(buf, cps);
15344     return TRUE;
15345   }
15346   return FALSE;
15347 }
15348
15349 int
15350 ParseOption(Option *opt, ChessProgramState *cps)
15351 // [HGM] options: process the string that defines an engine option, and determine
15352 // name, type, default value, and allowed value range
15353 {
15354         char *p, *q, buf[MSG_SIZ];
15355         int n, min = (-1)<<31, max = 1<<31, def;
15356
15357         if(p = strstr(opt->name, " -spin ")) {
15358             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15359             if(max < min) max = min; // enforce consistency
15360             if(def < min) def = min;
15361             if(def > max) def = max;
15362             opt->value = def;
15363             opt->min = min;
15364             opt->max = max;
15365             opt->type = Spin;
15366         } else if((p = strstr(opt->name, " -slider "))) {
15367             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15368             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15369             if(max < min) max = min; // enforce consistency
15370             if(def < min) def = min;
15371             if(def > max) def = max;
15372             opt->value = def;
15373             opt->min = min;
15374             opt->max = max;
15375             opt->type = Spin; // Slider;
15376         } else if((p = strstr(opt->name, " -string "))) {
15377             opt->textValue = p+9;
15378             opt->type = TextBox;
15379         } else if((p = strstr(opt->name, " -file "))) {
15380             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15381             opt->textValue = p+7;
15382             opt->type = FileName; // FileName;
15383         } else if((p = strstr(opt->name, " -path "))) {
15384             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15385             opt->textValue = p+7;
15386             opt->type = PathName; // PathName;
15387         } else if(p = strstr(opt->name, " -check ")) {
15388             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15389             opt->value = (def != 0);
15390             opt->type = CheckBox;
15391         } else if(p = strstr(opt->name, " -combo ")) {
15392             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15393             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15394             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15395             opt->value = n = 0;
15396             while(q = StrStr(q, " /// ")) {
15397                 n++; *q = 0;    // count choices, and null-terminate each of them
15398                 q += 5;
15399                 if(*q == '*') { // remember default, which is marked with * prefix
15400                     q++;
15401                     opt->value = n;
15402                 }
15403                 cps->comboList[cps->comboCnt++] = q;
15404             }
15405             cps->comboList[cps->comboCnt++] = NULL;
15406             opt->max = n + 1;
15407             opt->type = ComboBox;
15408         } else if(p = strstr(opt->name, " -button")) {
15409             opt->type = Button;
15410         } else if(p = strstr(opt->name, " -save")) {
15411             opt->type = SaveButton;
15412         } else return FALSE;
15413         *p = 0; // terminate option name
15414         // now look if the command-line options define a setting for this engine option.
15415         if(cps->optionSettings && cps->optionSettings[0])
15416             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15417         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15418           snprintf(buf, MSG_SIZ, "option %s", p);
15419                 if(p = strstr(buf, ",")) *p = 0;
15420                 if(q = strchr(buf, '=')) switch(opt->type) {
15421                     case ComboBox:
15422                         for(n=0; n<opt->max; n++)
15423                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15424                         break;
15425                     case TextBox:
15426                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15427                         break;
15428                     case Spin:
15429                     case CheckBox:
15430                         opt->value = atoi(q+1);
15431                     default:
15432                         break;
15433                 }
15434                 strcat(buf, "\n");
15435                 SendToProgram(buf, cps);
15436         }
15437         return TRUE;
15438 }
15439
15440 void
15441 FeatureDone(cps, val)
15442      ChessProgramState* cps;
15443      int val;
15444 {
15445   DelayedEventCallback cb = GetDelayedEvent();
15446   if ((cb == InitBackEnd3 && cps == &first) ||
15447       (cb == SettingsMenuIfReady && cps == &second) ||
15448       (cb == LoadEngine) ||
15449       (cb == TwoMachinesEventIfReady)) {
15450     CancelDelayedEvent();
15451     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15452   }
15453   cps->initDone = val;
15454 }
15455
15456 /* Parse feature command from engine */
15457 void
15458 ParseFeatures(args, cps)
15459      char* args;
15460      ChessProgramState *cps;
15461 {
15462   char *p = args;
15463   char *q;
15464   int val;
15465   char buf[MSG_SIZ];
15466
15467   for (;;) {
15468     while (*p == ' ') p++;
15469     if (*p == NULLCHAR) return;
15470
15471     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15472     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15473     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15474     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15475     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15476     if (BoolFeature(&p, "reuse", &val, cps)) {
15477       /* Engine can disable reuse, but can't enable it if user said no */
15478       if (!val) cps->reuse = FALSE;
15479       continue;
15480     }
15481     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15482     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15483       if (gameMode == TwoMachinesPlay) {
15484         DisplayTwoMachinesTitle();
15485       } else {
15486         DisplayTitle("");
15487       }
15488       continue;
15489     }
15490     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15491     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15492     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15493     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15494     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15495     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15496     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15497     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15498     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15499     if (IntFeature(&p, "done", &val, cps)) {
15500       FeatureDone(cps, val);
15501       continue;
15502     }
15503     /* Added by Tord: */
15504     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15505     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15506     /* End of additions by Tord */
15507
15508     /* [HGM] added features: */
15509     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15510     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15511     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15512     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15513     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15514     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15515     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15516         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15517           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15518             SendToProgram(buf, cps);
15519             continue;
15520         }
15521         if(cps->nrOptions >= MAX_OPTIONS) {
15522             cps->nrOptions--;
15523             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15524             DisplayError(buf, 0);
15525         }
15526         continue;
15527     }
15528     /* End of additions by HGM */
15529
15530     /* unknown feature: complain and skip */
15531     q = p;
15532     while (*q && *q != '=') q++;
15533     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15534     SendToProgram(buf, cps);
15535     p = q;
15536     if (*p == '=') {
15537       p++;
15538       if (*p == '\"') {
15539         p++;
15540         while (*p && *p != '\"') p++;
15541         if (*p == '\"') p++;
15542       } else {
15543         while (*p && *p != ' ') p++;
15544       }
15545     }
15546   }
15547
15548 }
15549
15550 void
15551 PeriodicUpdatesEvent(newState)
15552      int newState;
15553 {
15554     if (newState == appData.periodicUpdates)
15555       return;
15556
15557     appData.periodicUpdates=newState;
15558
15559     /* Display type changes, so update it now */
15560 //    DisplayAnalysis();
15561
15562     /* Get the ball rolling again... */
15563     if (newState) {
15564         AnalysisPeriodicEvent(1);
15565         StartAnalysisClock();
15566     }
15567 }
15568
15569 void
15570 PonderNextMoveEvent(newState)
15571      int newState;
15572 {
15573     if (newState == appData.ponderNextMove) return;
15574     if (gameMode == EditPosition) EditPositionDone(TRUE);
15575     if (newState) {
15576         SendToProgram("hard\n", &first);
15577         if (gameMode == TwoMachinesPlay) {
15578             SendToProgram("hard\n", &second);
15579         }
15580     } else {
15581         SendToProgram("easy\n", &first);
15582         thinkOutput[0] = NULLCHAR;
15583         if (gameMode == TwoMachinesPlay) {
15584             SendToProgram("easy\n", &second);
15585         }
15586     }
15587     appData.ponderNextMove = newState;
15588 }
15589
15590 void
15591 NewSettingEvent(option, feature, command, value)
15592      char *command;
15593      int option, value, *feature;
15594 {
15595     char buf[MSG_SIZ];
15596
15597     if (gameMode == EditPosition) EditPositionDone(TRUE);
15598     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15599     if(feature == NULL || *feature) SendToProgram(buf, &first);
15600     if (gameMode == TwoMachinesPlay) {
15601         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15602     }
15603 }
15604
15605 void
15606 ShowThinkingEvent()
15607 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15608 {
15609     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15610     int newState = appData.showThinking
15611         // [HGM] thinking: other features now need thinking output as well
15612         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15613
15614     if (oldState == newState) return;
15615     oldState = newState;
15616     if (gameMode == EditPosition) EditPositionDone(TRUE);
15617     if (oldState) {
15618         SendToProgram("post\n", &first);
15619         if (gameMode == TwoMachinesPlay) {
15620             SendToProgram("post\n", &second);
15621         }
15622     } else {
15623         SendToProgram("nopost\n", &first);
15624         thinkOutput[0] = NULLCHAR;
15625         if (gameMode == TwoMachinesPlay) {
15626             SendToProgram("nopost\n", &second);
15627         }
15628     }
15629 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15630 }
15631
15632 void
15633 AskQuestionEvent(title, question, replyPrefix, which)
15634      char *title; char *question; char *replyPrefix; char *which;
15635 {
15636   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15637   if (pr == NoProc) return;
15638   AskQuestion(title, question, replyPrefix, pr);
15639 }
15640
15641 void
15642 TypeInEvent(char firstChar)
15643 {
15644     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15645         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15646         gameMode == AnalyzeMode || gameMode == EditGame || 
15647         gameMode == EditPosition || gameMode == IcsExamining ||
15648         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15649         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15650                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15651                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15652         gameMode == Training) PopUpMoveDialog(firstChar);
15653 }
15654
15655 void
15656 TypeInDoneEvent(char *move)
15657 {
15658         Board board;
15659         int n, fromX, fromY, toX, toY;
15660         char promoChar;
15661         ChessMove moveType;
15662
15663         // [HGM] FENedit
15664         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15665                 EditPositionPasteFEN(move);
15666                 return;
15667         }
15668         // [HGM] movenum: allow move number to be typed in any mode
15669         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15670           ToNrEvent(2*n-1);
15671           return;
15672         }
15673
15674       if (gameMode != EditGame && currentMove != forwardMostMove && 
15675         gameMode != Training) {
15676         DisplayMoveError(_("Displayed move is not current"));
15677       } else {
15678         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15679           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15680         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15681         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15682           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15683           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15684         } else {
15685           DisplayMoveError(_("Could not parse move"));
15686         }
15687       }
15688 }
15689
15690 void
15691 DisplayMove(moveNumber)
15692      int moveNumber;
15693 {
15694     char message[MSG_SIZ];
15695     char res[MSG_SIZ];
15696     char cpThinkOutput[MSG_SIZ];
15697
15698     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15699
15700     if (moveNumber == forwardMostMove - 1 ||
15701         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15702
15703         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15704
15705         if (strchr(cpThinkOutput, '\n')) {
15706             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15707         }
15708     } else {
15709         *cpThinkOutput = NULLCHAR;
15710     }
15711
15712     /* [AS] Hide thinking from human user */
15713     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15714         *cpThinkOutput = NULLCHAR;
15715         if( thinkOutput[0] != NULLCHAR ) {
15716             int i;
15717
15718             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15719                 cpThinkOutput[i] = '.';
15720             }
15721             cpThinkOutput[i] = NULLCHAR;
15722             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15723         }
15724     }
15725
15726     if (moveNumber == forwardMostMove - 1 &&
15727         gameInfo.resultDetails != NULL) {
15728         if (gameInfo.resultDetails[0] == NULLCHAR) {
15729           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15730         } else {
15731           snprintf(res, MSG_SIZ, " {%s} %s",
15732                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15733         }
15734     } else {
15735         res[0] = NULLCHAR;
15736     }
15737
15738     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15739         DisplayMessage(res, cpThinkOutput);
15740     } else {
15741       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15742                 WhiteOnMove(moveNumber) ? " " : ".. ",
15743                 parseList[moveNumber], res);
15744         DisplayMessage(message, cpThinkOutput);
15745     }
15746 }
15747
15748 void
15749 DisplayComment(moveNumber, text)
15750      int moveNumber;
15751      char *text;
15752 {
15753     char title[MSG_SIZ];
15754
15755     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15756       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15757     } else {
15758       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15759               WhiteOnMove(moveNumber) ? " " : ".. ",
15760               parseList[moveNumber]);
15761     }
15762     if (text != NULL && (appData.autoDisplayComment || commentUp))
15763         CommentPopUp(title, text);
15764 }
15765
15766 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15767  * might be busy thinking or pondering.  It can be omitted if your
15768  * gnuchess is configured to stop thinking immediately on any user
15769  * input.  However, that gnuchess feature depends on the FIONREAD
15770  * ioctl, which does not work properly on some flavors of Unix.
15771  */
15772 void
15773 Attention(cps)
15774      ChessProgramState *cps;
15775 {
15776 #if ATTENTION
15777     if (!cps->useSigint) return;
15778     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15779     switch (gameMode) {
15780       case MachinePlaysWhite:
15781       case MachinePlaysBlack:
15782       case TwoMachinesPlay:
15783       case IcsPlayingWhite:
15784       case IcsPlayingBlack:
15785       case AnalyzeMode:
15786       case AnalyzeFile:
15787         /* Skip if we know it isn't thinking */
15788         if (!cps->maybeThinking) return;
15789         if (appData.debugMode)
15790           fprintf(debugFP, "Interrupting %s\n", cps->which);
15791         InterruptChildProcess(cps->pr);
15792         cps->maybeThinking = FALSE;
15793         break;
15794       default:
15795         break;
15796     }
15797 #endif /*ATTENTION*/
15798 }
15799
15800 int
15801 CheckFlags()
15802 {
15803     if (whiteTimeRemaining <= 0) {
15804         if (!whiteFlag) {
15805             whiteFlag = TRUE;
15806             if (appData.icsActive) {
15807                 if (appData.autoCallFlag &&
15808                     gameMode == IcsPlayingBlack && !blackFlag) {
15809                   SendToICS(ics_prefix);
15810                   SendToICS("flag\n");
15811                 }
15812             } else {
15813                 if (blackFlag) {
15814                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15815                 } else {
15816                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15817                     if (appData.autoCallFlag) {
15818                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15819                         return TRUE;
15820                     }
15821                 }
15822             }
15823         }
15824     }
15825     if (blackTimeRemaining <= 0) {
15826         if (!blackFlag) {
15827             blackFlag = TRUE;
15828             if (appData.icsActive) {
15829                 if (appData.autoCallFlag &&
15830                     gameMode == IcsPlayingWhite && !whiteFlag) {
15831                   SendToICS(ics_prefix);
15832                   SendToICS("flag\n");
15833                 }
15834             } else {
15835                 if (whiteFlag) {
15836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15837                 } else {
15838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15839                     if (appData.autoCallFlag) {
15840                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15841                         return TRUE;
15842                     }
15843                 }
15844             }
15845         }
15846     }
15847     return FALSE;
15848 }
15849
15850 void
15851 CheckTimeControl()
15852 {
15853     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15854         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15855
15856     /*
15857      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15858      */
15859     if ( !WhiteOnMove(forwardMostMove) ) {
15860         /* White made time control */
15861         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15862         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15863         /* [HGM] time odds: correct new time quota for time odds! */
15864                                             / WhitePlayer()->timeOdds;
15865         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15866     } else {
15867         lastBlack -= blackTimeRemaining;
15868         /* Black made time control */
15869         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15870                                             / WhitePlayer()->other->timeOdds;
15871         lastWhite = whiteTimeRemaining;
15872     }
15873 }
15874
15875 void
15876 DisplayBothClocks()
15877 {
15878     int wom = gameMode == EditPosition ?
15879       !blackPlaysFirst : WhiteOnMove(currentMove);
15880     DisplayWhiteClock(whiteTimeRemaining, wom);
15881     DisplayBlackClock(blackTimeRemaining, !wom);
15882 }
15883
15884
15885 /* Timekeeping seems to be a portability nightmare.  I think everyone
15886    has ftime(), but I'm really not sure, so I'm including some ifdefs
15887    to use other calls if you don't.  Clocks will be less accurate if
15888    you have neither ftime nor gettimeofday.
15889 */
15890
15891 /* VS 2008 requires the #include outside of the function */
15892 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15893 #include <sys/timeb.h>
15894 #endif
15895
15896 /* Get the current time as a TimeMark */
15897 void
15898 GetTimeMark(tm)
15899      TimeMark *tm;
15900 {
15901 #if HAVE_GETTIMEOFDAY
15902
15903     struct timeval timeVal;
15904     struct timezone timeZone;
15905
15906     gettimeofday(&timeVal, &timeZone);
15907     tm->sec = (long) timeVal.tv_sec;
15908     tm->ms = (int) (timeVal.tv_usec / 1000L);
15909
15910 #else /*!HAVE_GETTIMEOFDAY*/
15911 #if HAVE_FTIME
15912
15913 // include <sys/timeb.h> / moved to just above start of function
15914     struct timeb timeB;
15915
15916     ftime(&timeB);
15917     tm->sec = (long) timeB.time;
15918     tm->ms = (int) timeB.millitm;
15919
15920 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15921     tm->sec = (long) time(NULL);
15922     tm->ms = 0;
15923 #endif
15924 #endif
15925 }
15926
15927 /* Return the difference in milliseconds between two
15928    time marks.  We assume the difference will fit in a long!
15929 */
15930 long
15931 SubtractTimeMarks(tm2, tm1)
15932      TimeMark *tm2, *tm1;
15933 {
15934     return 1000L*(tm2->sec - tm1->sec) +
15935            (long) (tm2->ms - tm1->ms);
15936 }
15937
15938
15939 /*
15940  * Code to manage the game clocks.
15941  *
15942  * In tournament play, black starts the clock and then white makes a move.
15943  * We give the human user a slight advantage if he is playing white---the
15944  * clocks don't run until he makes his first move, so it takes zero time.
15945  * Also, we don't account for network lag, so we could get out of sync
15946  * with GNU Chess's clock -- but then, referees are always right.
15947  */
15948
15949 static TimeMark tickStartTM;
15950 static long intendedTickLength;
15951
15952 long
15953 NextTickLength(timeRemaining)
15954      long timeRemaining;
15955 {
15956     long nominalTickLength, nextTickLength;
15957
15958     if (timeRemaining > 0L && timeRemaining <= 10000L)
15959       nominalTickLength = 100L;
15960     else
15961       nominalTickLength = 1000L;
15962     nextTickLength = timeRemaining % nominalTickLength;
15963     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15964
15965     return nextTickLength;
15966 }
15967
15968 /* Adjust clock one minute up or down */
15969 void
15970 AdjustClock(Boolean which, int dir)
15971 {
15972     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15973     if(which) blackTimeRemaining += 60000*dir;
15974     else      whiteTimeRemaining += 60000*dir;
15975     DisplayBothClocks();
15976     adjustedClock = TRUE;
15977 }
15978
15979 /* Stop clocks and reset to a fresh time control */
15980 void
15981 ResetClocks()
15982 {
15983     (void) StopClockTimer();
15984     if (appData.icsActive) {
15985         whiteTimeRemaining = blackTimeRemaining = 0;
15986     } else if (searchTime) {
15987         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15988         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15989     } else { /* [HGM] correct new time quote for time odds */
15990         whiteTC = blackTC = fullTimeControlString;
15991         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15992         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15993     }
15994     if (whiteFlag || blackFlag) {
15995         DisplayTitle("");
15996         whiteFlag = blackFlag = FALSE;
15997     }
15998     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15999     DisplayBothClocks();
16000     adjustedClock = FALSE;
16001 }
16002
16003 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16004
16005 /* Decrement running clock by amount of time that has passed */
16006 void
16007 DecrementClocks()
16008 {
16009     long timeRemaining;
16010     long lastTickLength, fudge;
16011     TimeMark now;
16012
16013     if (!appData.clockMode) return;
16014     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16015
16016     GetTimeMark(&now);
16017
16018     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16019
16020     /* Fudge if we woke up a little too soon */
16021     fudge = intendedTickLength - lastTickLength;
16022     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16023
16024     if (WhiteOnMove(forwardMostMove)) {
16025         if(whiteNPS >= 0) lastTickLength = 0;
16026         timeRemaining = whiteTimeRemaining -= lastTickLength;
16027         if(timeRemaining < 0 && !appData.icsActive) {
16028             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16029             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16030                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16031                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16032             }
16033         }
16034         DisplayWhiteClock(whiteTimeRemaining - fudge,
16035                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16036     } else {
16037         if(blackNPS >= 0) lastTickLength = 0;
16038         timeRemaining = blackTimeRemaining -= lastTickLength;
16039         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16040             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16041             if(suddenDeath) {
16042                 blackStartMove = forwardMostMove;
16043                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16044             }
16045         }
16046         DisplayBlackClock(blackTimeRemaining - fudge,
16047                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16048     }
16049     if (CheckFlags()) return;
16050
16051     tickStartTM = now;
16052     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16053     StartClockTimer(intendedTickLength);
16054
16055     /* if the time remaining has fallen below the alarm threshold, sound the
16056      * alarm. if the alarm has sounded and (due to a takeback or time control
16057      * with increment) the time remaining has increased to a level above the
16058      * threshold, reset the alarm so it can sound again.
16059      */
16060
16061     if (appData.icsActive && appData.icsAlarm) {
16062
16063         /* make sure we are dealing with the user's clock */
16064         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16065                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16066            )) return;
16067
16068         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16069             alarmSounded = FALSE;
16070         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16071             PlayAlarmSound();
16072             alarmSounded = TRUE;
16073         }
16074     }
16075 }
16076
16077
16078 /* A player has just moved, so stop the previously running
16079    clock and (if in clock mode) start the other one.
16080    We redisplay both clocks in case we're in ICS mode, because
16081    ICS gives us an update to both clocks after every move.
16082    Note that this routine is called *after* forwardMostMove
16083    is updated, so the last fractional tick must be subtracted
16084    from the color that is *not* on move now.
16085 */
16086 void
16087 SwitchClocks(int newMoveNr)
16088 {
16089     long lastTickLength;
16090     TimeMark now;
16091     int flagged = FALSE;
16092
16093     GetTimeMark(&now);
16094
16095     if (StopClockTimer() && appData.clockMode) {
16096         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16097         if (!WhiteOnMove(forwardMostMove)) {
16098             if(blackNPS >= 0) lastTickLength = 0;
16099             blackTimeRemaining -= lastTickLength;
16100            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16101 //         if(pvInfoList[forwardMostMove].time == -1)
16102                  pvInfoList[forwardMostMove].time =               // use GUI time
16103                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16104         } else {
16105            if(whiteNPS >= 0) lastTickLength = 0;
16106            whiteTimeRemaining -= lastTickLength;
16107            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16108 //         if(pvInfoList[forwardMostMove].time == -1)
16109                  pvInfoList[forwardMostMove].time =
16110                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16111         }
16112         flagged = CheckFlags();
16113     }
16114     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16115     CheckTimeControl();
16116
16117     if (flagged || !appData.clockMode) return;
16118
16119     switch (gameMode) {
16120       case MachinePlaysBlack:
16121       case MachinePlaysWhite:
16122       case BeginningOfGame:
16123         if (pausing) return;
16124         break;
16125
16126       case EditGame:
16127       case PlayFromGameFile:
16128       case IcsExamining:
16129         return;
16130
16131       default:
16132         break;
16133     }
16134
16135     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16136         if(WhiteOnMove(forwardMostMove))
16137              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16138         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16139     }
16140
16141     tickStartTM = now;
16142     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16143       whiteTimeRemaining : blackTimeRemaining);
16144     StartClockTimer(intendedTickLength);
16145 }
16146
16147
16148 /* Stop both clocks */
16149 void
16150 StopClocks()
16151 {
16152     long lastTickLength;
16153     TimeMark now;
16154
16155     if (!StopClockTimer()) return;
16156     if (!appData.clockMode) return;
16157
16158     GetTimeMark(&now);
16159
16160     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16161     if (WhiteOnMove(forwardMostMove)) {
16162         if(whiteNPS >= 0) lastTickLength = 0;
16163         whiteTimeRemaining -= lastTickLength;
16164         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16165     } else {
16166         if(blackNPS >= 0) lastTickLength = 0;
16167         blackTimeRemaining -= lastTickLength;
16168         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16169     }
16170     CheckFlags();
16171 }
16172
16173 /* Start clock of player on move.  Time may have been reset, so
16174    if clock is already running, stop and restart it. */
16175 void
16176 StartClocks()
16177 {
16178     (void) StopClockTimer(); /* in case it was running already */
16179     DisplayBothClocks();
16180     if (CheckFlags()) return;
16181
16182     if (!appData.clockMode) return;
16183     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16184
16185     GetTimeMark(&tickStartTM);
16186     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16187       whiteTimeRemaining : blackTimeRemaining);
16188
16189    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16190     whiteNPS = blackNPS = -1;
16191     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16192        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16193         whiteNPS = first.nps;
16194     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16195        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16196         blackNPS = first.nps;
16197     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16198         whiteNPS = second.nps;
16199     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16200         blackNPS = second.nps;
16201     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16202
16203     StartClockTimer(intendedTickLength);
16204 }
16205
16206 char *
16207 TimeString(ms)
16208      long ms;
16209 {
16210     long second, minute, hour, day;
16211     char *sign = "";
16212     static char buf[32];
16213
16214     if (ms > 0 && ms <= 9900) {
16215       /* convert milliseconds to tenths, rounding up */
16216       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16217
16218       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16219       return buf;
16220     }
16221
16222     /* convert milliseconds to seconds, rounding up */
16223     /* use floating point to avoid strangeness of integer division
16224        with negative dividends on many machines */
16225     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16226
16227     if (second < 0) {
16228         sign = "-";
16229         second = -second;
16230     }
16231
16232     day = second / (60 * 60 * 24);
16233     second = second % (60 * 60 * 24);
16234     hour = second / (60 * 60);
16235     second = second % (60 * 60);
16236     minute = second / 60;
16237     second = second % 60;
16238
16239     if (day > 0)
16240       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16241               sign, day, hour, minute, second);
16242     else if (hour > 0)
16243       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16244     else
16245       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16246
16247     return buf;
16248 }
16249
16250
16251 /*
16252  * This is necessary because some C libraries aren't ANSI C compliant yet.
16253  */
16254 char *
16255 StrStr(string, match)
16256      char *string, *match;
16257 {
16258     int i, length;
16259
16260     length = strlen(match);
16261
16262     for (i = strlen(string) - length; i >= 0; i--, string++)
16263       if (!strncmp(match, string, length))
16264         return string;
16265
16266     return NULL;
16267 }
16268
16269 char *
16270 StrCaseStr(string, match)
16271      char *string, *match;
16272 {
16273     int i, j, length;
16274
16275     length = strlen(match);
16276
16277     for (i = strlen(string) - length; i >= 0; i--, string++) {
16278         for (j = 0; j < length; j++) {
16279             if (ToLower(match[j]) != ToLower(string[j]))
16280               break;
16281         }
16282         if (j == length) return string;
16283     }
16284
16285     return NULL;
16286 }
16287
16288 #ifndef _amigados
16289 int
16290 StrCaseCmp(s1, s2)
16291      char *s1, *s2;
16292 {
16293     char c1, c2;
16294
16295     for (;;) {
16296         c1 = ToLower(*s1++);
16297         c2 = ToLower(*s2++);
16298         if (c1 > c2) return 1;
16299         if (c1 < c2) return -1;
16300         if (c1 == NULLCHAR) return 0;
16301     }
16302 }
16303
16304
16305 int
16306 ToLower(c)
16307      int c;
16308 {
16309     return isupper(c) ? tolower(c) : c;
16310 }
16311
16312
16313 int
16314 ToUpper(c)
16315      int c;
16316 {
16317     return islower(c) ? toupper(c) : c;
16318 }
16319 #endif /* !_amigados    */
16320
16321 char *
16322 StrSave(s)
16323      char *s;
16324 {
16325   char *ret;
16326
16327   if ((ret = (char *) malloc(strlen(s) + 1)))
16328     {
16329       safeStrCpy(ret, s, strlen(s)+1);
16330     }
16331   return ret;
16332 }
16333
16334 char *
16335 StrSavePtr(s, savePtr)
16336      char *s, **savePtr;
16337 {
16338     if (*savePtr) {
16339         free(*savePtr);
16340     }
16341     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16342       safeStrCpy(*savePtr, s, strlen(s)+1);
16343     }
16344     return(*savePtr);
16345 }
16346
16347 char *
16348 PGNDate()
16349 {
16350     time_t clock;
16351     struct tm *tm;
16352     char buf[MSG_SIZ];
16353
16354     clock = time((time_t *)NULL);
16355     tm = localtime(&clock);
16356     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16357             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16358     return StrSave(buf);
16359 }
16360
16361
16362 char *
16363 PositionToFEN(move, overrideCastling)
16364      int move;
16365      char *overrideCastling;
16366 {
16367     int i, j, fromX, fromY, toX, toY;
16368     int whiteToPlay;
16369     char buf[MSG_SIZ];
16370     char *p, *q;
16371     int emptycount;
16372     ChessSquare piece;
16373
16374     whiteToPlay = (gameMode == EditPosition) ?
16375       !blackPlaysFirst : (move % 2 == 0);
16376     p = buf;
16377
16378     /* Piece placement data */
16379     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16380         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16381         emptycount = 0;
16382         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16383             if (boards[move][i][j] == EmptySquare) {
16384                 emptycount++;
16385             } else { ChessSquare piece = boards[move][i][j];
16386                 if (emptycount > 0) {
16387                     if(emptycount<10) /* [HGM] can be >= 10 */
16388                         *p++ = '0' + emptycount;
16389                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16390                     emptycount = 0;
16391                 }
16392                 if(PieceToChar(piece) == '+') {
16393                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16394                     *p++ = '+';
16395                     piece = (ChessSquare)(DEMOTED piece);
16396                 }
16397                 *p++ = PieceToChar(piece);
16398                 if(p[-1] == '~') {
16399                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16400                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16401                     *p++ = '~';
16402                 }
16403             }
16404         }
16405         if (emptycount > 0) {
16406             if(emptycount<10) /* [HGM] can be >= 10 */
16407                 *p++ = '0' + emptycount;
16408             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16409             emptycount = 0;
16410         }
16411         *p++ = '/';
16412     }
16413     *(p - 1) = ' ';
16414
16415     /* [HGM] print Crazyhouse or Shogi holdings */
16416     if( gameInfo.holdingsWidth ) {
16417         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16418         q = p;
16419         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16420             piece = boards[move][i][BOARD_WIDTH-1];
16421             if( piece != EmptySquare )
16422               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16423                   *p++ = PieceToChar(piece);
16424         }
16425         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16426             piece = boards[move][BOARD_HEIGHT-i-1][0];
16427             if( piece != EmptySquare )
16428               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16429                   *p++ = PieceToChar(piece);
16430         }
16431
16432         if( q == p ) *p++ = '-';
16433         *p++ = ']';
16434         *p++ = ' ';
16435     }
16436
16437     /* Active color */
16438     *p++ = whiteToPlay ? 'w' : 'b';
16439     *p++ = ' ';
16440
16441   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16442     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16443   } else {
16444   if(nrCastlingRights) {
16445      q = p;
16446      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16447        /* [HGM] write directly from rights */
16448            if(boards[move][CASTLING][2] != NoRights &&
16449               boards[move][CASTLING][0] != NoRights   )
16450                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16451            if(boards[move][CASTLING][2] != NoRights &&
16452               boards[move][CASTLING][1] != NoRights   )
16453                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16454            if(boards[move][CASTLING][5] != NoRights &&
16455               boards[move][CASTLING][3] != NoRights   )
16456                 *p++ = boards[move][CASTLING][3] + AAA;
16457            if(boards[move][CASTLING][5] != NoRights &&
16458               boards[move][CASTLING][4] != NoRights   )
16459                 *p++ = boards[move][CASTLING][4] + AAA;
16460      } else {
16461
16462         /* [HGM] write true castling rights */
16463         if( nrCastlingRights == 6 ) {
16464             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16465                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16466             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16467                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16468             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16469                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16470             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16471                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16472         }
16473      }
16474      if (q == p) *p++ = '-'; /* No castling rights */
16475      *p++ = ' ';
16476   }
16477
16478   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16479      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16480     /* En passant target square */
16481     if (move > backwardMostMove) {
16482         fromX = moveList[move - 1][0] - AAA;
16483         fromY = moveList[move - 1][1] - ONE;
16484         toX = moveList[move - 1][2] - AAA;
16485         toY = moveList[move - 1][3] - ONE;
16486         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16487             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16488             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16489             fromX == toX) {
16490             /* 2-square pawn move just happened */
16491             *p++ = toX + AAA;
16492             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16493         } else {
16494             *p++ = '-';
16495         }
16496     } else if(move == backwardMostMove) {
16497         // [HGM] perhaps we should always do it like this, and forget the above?
16498         if((signed char)boards[move][EP_STATUS] >= 0) {
16499             *p++ = boards[move][EP_STATUS] + AAA;
16500             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16501         } else {
16502             *p++ = '-';
16503         }
16504     } else {
16505         *p++ = '-';
16506     }
16507     *p++ = ' ';
16508   }
16509   }
16510
16511     /* [HGM] find reversible plies */
16512     {   int i = 0, j=move;
16513
16514         if (appData.debugMode) { int k;
16515             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16516             for(k=backwardMostMove; k<=forwardMostMove; k++)
16517                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16518
16519         }
16520
16521         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16522         if( j == backwardMostMove ) i += initialRulePlies;
16523         sprintf(p, "%d ", i);
16524         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16525     }
16526     /* Fullmove number */
16527     sprintf(p, "%d", (move / 2) + 1);
16528
16529     return StrSave(buf);
16530 }
16531
16532 Boolean
16533 ParseFEN(board, blackPlaysFirst, fen)
16534     Board board;
16535      int *blackPlaysFirst;
16536      char *fen;
16537 {
16538     int i, j;
16539     char *p, c;
16540     int emptycount;
16541     ChessSquare piece;
16542
16543     p = fen;
16544
16545     /* [HGM] by default clear Crazyhouse holdings, if present */
16546     if(gameInfo.holdingsWidth) {
16547        for(i=0; i<BOARD_HEIGHT; i++) {
16548            board[i][0]             = EmptySquare; /* black holdings */
16549            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16550            board[i][1]             = (ChessSquare) 0; /* black counts */
16551            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16552        }
16553     }
16554
16555     /* Piece placement data */
16556     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16557         j = 0;
16558         for (;;) {
16559             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16560                 if (*p == '/') p++;
16561                 emptycount = gameInfo.boardWidth - j;
16562                 while (emptycount--)
16563                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16564                 break;
16565 #if(BOARD_FILES >= 10)
16566             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16567                 p++; emptycount=10;
16568                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16569                 while (emptycount--)
16570                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16571 #endif
16572             } else if (isdigit(*p)) {
16573                 emptycount = *p++ - '0';
16574                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16575                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16576                 while (emptycount--)
16577                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16578             } else if (*p == '+' || isalpha(*p)) {
16579                 if (j >= gameInfo.boardWidth) return FALSE;
16580                 if(*p=='+') {
16581                     piece = CharToPiece(*++p);
16582                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16583                     piece = (ChessSquare) (PROMOTED piece ); p++;
16584                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16585                 } else piece = CharToPiece(*p++);
16586
16587                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16588                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16589                     piece = (ChessSquare) (PROMOTED piece);
16590                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16591                     p++;
16592                 }
16593                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16594             } else {
16595                 return FALSE;
16596             }
16597         }
16598     }
16599     while (*p == '/' || *p == ' ') p++;
16600
16601     /* [HGM] look for Crazyhouse holdings here */
16602     while(*p==' ') p++;
16603     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16604         if(*p == '[') p++;
16605         if(*p == '-' ) p++; /* empty holdings */ else {
16606             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16607             /* if we would allow FEN reading to set board size, we would   */
16608             /* have to add holdings and shift the board read so far here   */
16609             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16610                 p++;
16611                 if((int) piece >= (int) BlackPawn ) {
16612                     i = (int)piece - (int)BlackPawn;
16613                     i = PieceToNumber((ChessSquare)i);
16614                     if( i >= gameInfo.holdingsSize ) return FALSE;
16615                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16616                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16617                 } else {
16618                     i = (int)piece - (int)WhitePawn;
16619                     i = PieceToNumber((ChessSquare)i);
16620                     if( i >= gameInfo.holdingsSize ) return FALSE;
16621                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16622                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16623                 }
16624             }
16625         }
16626         if(*p == ']') p++;
16627     }
16628
16629     while(*p == ' ') p++;
16630
16631     /* Active color */
16632     c = *p++;
16633     if(appData.colorNickNames) {
16634       if( c == appData.colorNickNames[0] ) c = 'w'; else
16635       if( c == appData.colorNickNames[1] ) c = 'b';
16636     }
16637     switch (c) {
16638       case 'w':
16639         *blackPlaysFirst = FALSE;
16640         break;
16641       case 'b':
16642         *blackPlaysFirst = TRUE;
16643         break;
16644       default:
16645         return FALSE;
16646     }
16647
16648     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16649     /* return the extra info in global variiables             */
16650
16651     /* set defaults in case FEN is incomplete */
16652     board[EP_STATUS] = EP_UNKNOWN;
16653     for(i=0; i<nrCastlingRights; i++ ) {
16654         board[CASTLING][i] =
16655             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16656     }   /* assume possible unless obviously impossible */
16657     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16658     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16659     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16660                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16661     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16662     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16663     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16664                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16665     FENrulePlies = 0;
16666
16667     while(*p==' ') p++;
16668     if(nrCastlingRights) {
16669       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16670           /* castling indicator present, so default becomes no castlings */
16671           for(i=0; i<nrCastlingRights; i++ ) {
16672                  board[CASTLING][i] = NoRights;
16673           }
16674       }
16675       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16676              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16677              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16678              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16679         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16680
16681         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16682             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16683             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16684         }
16685         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16686             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16687         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16688                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16689         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16690                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16691         switch(c) {
16692           case'K':
16693               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16694               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16695               board[CASTLING][2] = whiteKingFile;
16696               break;
16697           case'Q':
16698               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16699               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16700               board[CASTLING][2] = whiteKingFile;
16701               break;
16702           case'k':
16703               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16704               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16705               board[CASTLING][5] = blackKingFile;
16706               break;
16707           case'q':
16708               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16709               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16710               board[CASTLING][5] = blackKingFile;
16711           case '-':
16712               break;
16713           default: /* FRC castlings */
16714               if(c >= 'a') { /* black rights */
16715                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16716                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16717                   if(i == BOARD_RGHT) break;
16718                   board[CASTLING][5] = i;
16719                   c -= AAA;
16720                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16721                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16722                   if(c > i)
16723                       board[CASTLING][3] = c;
16724                   else
16725                       board[CASTLING][4] = c;
16726               } else { /* white rights */
16727                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16728                     if(board[0][i] == WhiteKing) break;
16729                   if(i == BOARD_RGHT) break;
16730                   board[CASTLING][2] = i;
16731                   c -= AAA - 'a' + 'A';
16732                   if(board[0][c] >= WhiteKing) break;
16733                   if(c > i)
16734                       board[CASTLING][0] = c;
16735                   else
16736                       board[CASTLING][1] = c;
16737               }
16738         }
16739       }
16740       for(i=0; i<nrCastlingRights; i++)
16741         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16742     if (appData.debugMode) {
16743         fprintf(debugFP, "FEN castling rights:");
16744         for(i=0; i<nrCastlingRights; i++)
16745         fprintf(debugFP, " %d", board[CASTLING][i]);
16746         fprintf(debugFP, "\n");
16747     }
16748
16749       while(*p==' ') p++;
16750     }
16751
16752     /* read e.p. field in games that know e.p. capture */
16753     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16754        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16755       if(*p=='-') {
16756         p++; board[EP_STATUS] = EP_NONE;
16757       } else {
16758          char c = *p++ - AAA;
16759
16760          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16761          if(*p >= '0' && *p <='9') p++;
16762          board[EP_STATUS] = c;
16763       }
16764     }
16765
16766
16767     if(sscanf(p, "%d", &i) == 1) {
16768         FENrulePlies = i; /* 50-move ply counter */
16769         /* (The move number is still ignored)    */
16770     }
16771
16772     return TRUE;
16773 }
16774
16775 void
16776 EditPositionPasteFEN(char *fen)
16777 {
16778   if (fen != NULL) {
16779     Board initial_position;
16780
16781     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16782       DisplayError(_("Bad FEN position in clipboard"), 0);
16783       return ;
16784     } else {
16785       int savedBlackPlaysFirst = blackPlaysFirst;
16786       EditPositionEvent();
16787       blackPlaysFirst = savedBlackPlaysFirst;
16788       CopyBoard(boards[0], initial_position);
16789       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16790       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16791       DisplayBothClocks();
16792       DrawPosition(FALSE, boards[currentMove]);
16793     }
16794   }
16795 }
16796
16797 static char cseq[12] = "\\   ";
16798
16799 Boolean set_cont_sequence(char *new_seq)
16800 {
16801     int len;
16802     Boolean ret;
16803
16804     // handle bad attempts to set the sequence
16805         if (!new_seq)
16806                 return 0; // acceptable error - no debug
16807
16808     len = strlen(new_seq);
16809     ret = (len > 0) && (len < sizeof(cseq));
16810     if (ret)
16811       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16812     else if (appData.debugMode)
16813       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16814     return ret;
16815 }
16816
16817 /*
16818     reformat a source message so words don't cross the width boundary.  internal
16819     newlines are not removed.  returns the wrapped size (no null character unless
16820     included in source message).  If dest is NULL, only calculate the size required
16821     for the dest buffer.  lp argument indicats line position upon entry, and it's
16822     passed back upon exit.
16823 */
16824 int wrap(char *dest, char *src, int count, int width, int *lp)
16825 {
16826     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16827
16828     cseq_len = strlen(cseq);
16829     old_line = line = *lp;
16830     ansi = len = clen = 0;
16831
16832     for (i=0; i < count; i++)
16833     {
16834         if (src[i] == '\033')
16835             ansi = 1;
16836
16837         // if we hit the width, back up
16838         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16839         {
16840             // store i & len in case the word is too long
16841             old_i = i, old_len = len;
16842
16843             // find the end of the last word
16844             while (i && src[i] != ' ' && src[i] != '\n')
16845             {
16846                 i--;
16847                 len--;
16848             }
16849
16850             // word too long?  restore i & len before splitting it
16851             if ((old_i-i+clen) >= width)
16852             {
16853                 i = old_i;
16854                 len = old_len;
16855             }
16856
16857             // extra space?
16858             if (i && src[i-1] == ' ')
16859                 len--;
16860
16861             if (src[i] != ' ' && src[i] != '\n')
16862             {
16863                 i--;
16864                 if (len)
16865                     len--;
16866             }
16867
16868             // now append the newline and continuation sequence
16869             if (dest)
16870                 dest[len] = '\n';
16871             len++;
16872             if (dest)
16873                 strncpy(dest+len, cseq, cseq_len);
16874             len += cseq_len;
16875             line = cseq_len;
16876             clen = cseq_len;
16877             continue;
16878         }
16879
16880         if (dest)
16881             dest[len] = src[i];
16882         len++;
16883         if (!ansi)
16884             line++;
16885         if (src[i] == '\n')
16886             line = 0;
16887         if (src[i] == 'm')
16888             ansi = 0;
16889     }
16890     if (dest && appData.debugMode)
16891     {
16892         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16893             count, width, line, len, *lp);
16894         show_bytes(debugFP, src, count);
16895         fprintf(debugFP, "\ndest: ");
16896         show_bytes(debugFP, dest, len);
16897         fprintf(debugFP, "\n");
16898     }
16899     *lp = dest ? line : old_line;
16900
16901     return len;
16902 }
16903
16904 // [HGM] vari: routines for shelving variations
16905 Boolean modeRestore = FALSE;
16906
16907 void
16908 PushInner(int firstMove, int lastMove)
16909 {
16910         int i, j, nrMoves = lastMove - firstMove;
16911
16912         // push current tail of game on stack
16913         savedResult[storedGames] = gameInfo.result;
16914         savedDetails[storedGames] = gameInfo.resultDetails;
16915         gameInfo.resultDetails = NULL;
16916         savedFirst[storedGames] = firstMove;
16917         savedLast [storedGames] = lastMove;
16918         savedFramePtr[storedGames] = framePtr;
16919         framePtr -= nrMoves; // reserve space for the boards
16920         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16921             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16922             for(j=0; j<MOVE_LEN; j++)
16923                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16924             for(j=0; j<2*MOVE_LEN; j++)
16925                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16926             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16927             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16928             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16929             pvInfoList[firstMove+i-1].depth = 0;
16930             commentList[framePtr+i] = commentList[firstMove+i];
16931             commentList[firstMove+i] = NULL;
16932         }
16933
16934         storedGames++;
16935         forwardMostMove = firstMove; // truncate game so we can start variation
16936 }
16937
16938 void
16939 PushTail(int firstMove, int lastMove)
16940 {
16941         if(appData.icsActive) { // only in local mode
16942                 forwardMostMove = currentMove; // mimic old ICS behavior
16943                 return;
16944         }
16945         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16946
16947         PushInner(firstMove, lastMove);
16948         if(storedGames == 1) GreyRevert(FALSE);
16949         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16950 }
16951
16952 void
16953 PopInner(Boolean annotate)
16954 {
16955         int i, j, nrMoves;
16956         char buf[8000], moveBuf[20];
16957
16958         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16959         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16960         nrMoves = savedLast[storedGames] - currentMove;
16961         if(annotate) {
16962                 int cnt = 10;
16963                 if(!WhiteOnMove(currentMove))
16964                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16965                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16966                 for(i=currentMove; i<forwardMostMove; i++) {
16967                         if(WhiteOnMove(i))
16968                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16969                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16970                         strcat(buf, moveBuf);
16971                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16972                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16973                 }
16974                 strcat(buf, ")");
16975         }
16976         for(i=1; i<=nrMoves; i++) { // copy last variation back
16977             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16978             for(j=0; j<MOVE_LEN; j++)
16979                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16980             for(j=0; j<2*MOVE_LEN; j++)
16981                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16982             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16983             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16984             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16985             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16986             commentList[currentMove+i] = commentList[framePtr+i];
16987             commentList[framePtr+i] = NULL;
16988         }
16989         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16990         framePtr = savedFramePtr[storedGames];
16991         gameInfo.result = savedResult[storedGames];
16992         if(gameInfo.resultDetails != NULL) {
16993             free(gameInfo.resultDetails);
16994       }
16995         gameInfo.resultDetails = savedDetails[storedGames];
16996         forwardMostMove = currentMove + nrMoves;
16997 }
16998
16999 Boolean
17000 PopTail(Boolean annotate)
17001 {
17002         if(appData.icsActive) return FALSE; // only in local mode
17003         if(!storedGames) return FALSE; // sanity
17004         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17005
17006         PopInner(annotate);
17007         if(currentMove < forwardMostMove) ForwardEvent(); else
17008         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17009
17010         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17011         return TRUE;
17012 }
17013
17014 void
17015 CleanupTail()
17016 {       // remove all shelved variations
17017         int i;
17018         for(i=0; i<storedGames; i++) {
17019             if(savedDetails[i])
17020                 free(savedDetails[i]);
17021             savedDetails[i] = NULL;
17022         }
17023         for(i=framePtr; i<MAX_MOVES; i++) {
17024                 if(commentList[i]) free(commentList[i]);
17025                 commentList[i] = NULL;
17026         }
17027         framePtr = MAX_MOVES-1;
17028         storedGames = 0;
17029 }
17030
17031 void
17032 LoadVariation(int index, char *text)
17033 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17034         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17035         int level = 0, move;
17036
17037         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17038         // first find outermost bracketing variation
17039         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17040             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17041                 if(*p == '{') wait = '}'; else
17042                 if(*p == '[') wait = ']'; else
17043                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17044                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17045             }
17046             if(*p == wait) wait = NULLCHAR; // closing ]} found
17047             p++;
17048         }
17049         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17050         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17051         end[1] = NULLCHAR; // clip off comment beyond variation
17052         ToNrEvent(currentMove-1);
17053         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17054         // kludge: use ParsePV() to append variation to game
17055         move = currentMove;
17056         ParsePV(start, TRUE, TRUE);
17057         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17058         ClearPremoveHighlights();
17059         CommentPopDown();
17060         ToNrEvent(currentMove+1);
17061 }
17062