Put promo-suffix on ICS move in variant seirawan castlings
[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     else suffix[0] = NULLCHAR;
4960
4961     switch (moveType) {
4962       default:
4963         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4964                 (int)moveType, fromX, fromY, toX, toY);
4965         DisplayError(user_move + strlen("say "), 0);
4966         break;
4967       case WhiteKingSideCastle:
4968       case BlackKingSideCastle:
4969       case WhiteQueenSideCastleWild:
4970       case BlackQueenSideCastleWild:
4971       /* PUSH Fabien */
4972       case WhiteHSideCastleFR:
4973       case BlackHSideCastleFR:
4974       /* POP Fabien */
4975         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4976         break;
4977       case WhiteQueenSideCastle:
4978       case BlackQueenSideCastle:
4979       case WhiteKingSideCastleWild:
4980       case BlackKingSideCastleWild:
4981       /* PUSH Fabien */
4982       case WhiteASideCastleFR:
4983       case BlackASideCastleFR:
4984       /* POP Fabien */
4985         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4986         break;
4987       case WhiteNonPromotion:
4988       case BlackNonPromotion:
4989         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4990         break;
4991       case WhitePromotion:
4992       case BlackPromotion:
4993         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4994           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4995                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4996                 PieceToChar(WhiteFerz));
4997         else if(gameInfo.variant == VariantGreat)
4998           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4999                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5000                 PieceToChar(WhiteMan));
5001         else
5002           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5004                 promoChar);
5005         break;
5006       case WhiteDrop:
5007       case BlackDrop:
5008       drop:
5009         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5010                  ToUpper(PieceToChar((ChessSquare) fromX)),
5011                  AAA + toX, ONE + toY);
5012         break;
5013       case IllegalMove:  /* could be a variant we don't quite understand */
5014         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5015       case NormalMove:
5016       case WhiteCapturesEnPassant:
5017       case BlackCapturesEnPassant:
5018         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5020         break;
5021     }
5022     SendToICS(user_move);
5023     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5024         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5025 }
5026
5027 void
5028 UploadGameEvent()
5029 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5030     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5031     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5032     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5033         DisplayError("You cannot do this while you are playing or observing", 0);
5034         return;
5035     }
5036     if(gameMode != IcsExamining) { // is this ever not the case?
5037         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5038
5039         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5040           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5041         } else { // on FICS we must first go to general examine mode
5042           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5043         }
5044         if(gameInfo.variant != VariantNormal) {
5045             // try figure out wild number, as xboard names are not always valid on ICS
5046             for(i=1; i<=36; i++) {
5047               snprintf(buf, MSG_SIZ, "wild/%d", i);
5048                 if(StringToVariant(buf) == gameInfo.variant) break;
5049             }
5050             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5051             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5052             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5053         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5054         SendToICS(ics_prefix);
5055         SendToICS(buf);
5056         if(startedFromSetupPosition || backwardMostMove != 0) {
5057           fen = PositionToFEN(backwardMostMove, NULL);
5058           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5059             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5060             SendToICS(buf);
5061           } else { // FICS: everything has to set by separate bsetup commands
5062             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5063             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5064             SendToICS(buf);
5065             if(!WhiteOnMove(backwardMostMove)) {
5066                 SendToICS("bsetup tomove black\n");
5067             }
5068             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5069             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5070             SendToICS(buf);
5071             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5072             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = boards[backwardMostMove][EP_STATUS];
5075             if(i >= 0) { // set e.p.
5076               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5077                 SendToICS(buf);
5078             }
5079             bsetup++;
5080           }
5081         }
5082       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5083             SendToICS("bsetup done\n"); // switch to normal examining.
5084     }
5085     for(i = backwardMostMove; i<last; i++) {
5086         char buf[20];
5087         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5088         SendToICS(buf);
5089     }
5090     SendToICS(ics_prefix);
5091     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5092 }
5093
5094 void
5095 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5096      int rf, ff, rt, ft;
5097      char promoChar;
5098      char move[7];
5099 {
5100     if (rf == DROP_RANK) {
5101       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5102       sprintf(move, "%c@%c%c\n",
5103                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5104     } else {
5105         if (promoChar == 'x' || promoChar == NULLCHAR) {
5106           sprintf(move, "%c%c%c%c\n",
5107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5108         } else {
5109             sprintf(move, "%c%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5111         }
5112     }
5113 }
5114
5115 void
5116 ProcessICSInitScript(f)
5117      FILE *f;
5118 {
5119     char buf[MSG_SIZ];
5120
5121     while (fgets(buf, MSG_SIZ, f)) {
5122         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5123     }
5124
5125     fclose(f);
5126 }
5127
5128
5129 static int lastX, lastY, selectFlag, dragging;
5130
5131 void
5132 Sweep(int step)
5133 {
5134     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5135     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5136     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5137     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5138     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5139     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5140     do {
5141         promoSweep -= step;
5142         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5143         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5144         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5145         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5146         if(!step) step = -1;
5147     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5148             appData.testLegality && (promoSweep == king ||
5149             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5150     ChangeDragPiece(promoSweep);
5151 }
5152
5153 int PromoScroll(int x, int y)
5154 {
5155   int step = 0;
5156
5157   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5158   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5159   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5160   if(!step) return FALSE;
5161   lastX = x; lastY = y;
5162   if((promoSweep < BlackPawn) == flipView) step = -step;
5163   if(step > 0) selectFlag = 1;
5164   if(!selectFlag) Sweep(step);
5165   return FALSE;
5166 }
5167
5168 void
5169 NextPiece(int step)
5170 {
5171     ChessSquare piece = boards[currentMove][toY][toX];
5172     do {
5173         pieceSweep -= step;
5174         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5175         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5176         if(!step) step = -1;
5177     } while(PieceToChar(pieceSweep) == '.');
5178     boards[currentMove][toY][toX] = pieceSweep;
5179     DrawPosition(FALSE, boards[currentMove]);
5180     boards[currentMove][toY][toX] = piece;
5181 }
5182 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5183 void
5184 AlphaRank(char *move, int n)
5185 {
5186 //    char *p = move, c; int x, y;
5187
5188     if (appData.debugMode) {
5189         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5190     }
5191
5192     if(move[1]=='*' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         move[1] = '@';
5196         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5197         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5198     } else
5199     if(move[0]>='0' && move[0]<='9' &&
5200        move[1]>='a' && move[1]<='x' &&
5201        move[2]>='0' && move[2]<='9' &&
5202        move[3]>='a' && move[3]<='x'    ) {
5203         /* input move, Shogi -> normal */
5204         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5205         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5208     } else
5209     if(move[1]=='@' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212         move[1] = '*';
5213         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5215     } else
5216     if(
5217        move[0]>='a' && move[0]<='x' &&
5218        move[3]>='0' && move[3]<='9' &&
5219        move[2]>='a' && move[2]<='x'    ) {
5220          /* output move, normal -> Shogi */
5221         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5225         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5226     }
5227     if (appData.debugMode) {
5228         fprintf(debugFP, "   out = '%s'\n", move);
5229     }
5230 }
5231
5232 char yy_textstr[8000];
5233
5234 /* Parser for moves from gnuchess, ICS, or user typein box */
5235 Boolean
5236 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5237      char *move;
5238      int moveNum;
5239      ChessMove *moveType;
5240      int *fromX, *fromY, *toX, *toY;
5241      char *promoChar;
5242 {
5243     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5244
5245     switch (*moveType) {
5246       case WhitePromotion:
5247       case BlackPromotion:
5248       case WhiteNonPromotion:
5249       case BlackNonPromotion:
5250       case NormalMove:
5251       case WhiteCapturesEnPassant:
5252       case BlackCapturesEnPassant:
5253       case WhiteKingSideCastle:
5254       case WhiteQueenSideCastle:
5255       case BlackKingSideCastle:
5256       case BlackQueenSideCastle:
5257       case WhiteKingSideCastleWild:
5258       case WhiteQueenSideCastleWild:
5259       case BlackKingSideCastleWild:
5260       case BlackQueenSideCastleWild:
5261       /* Code added by Tord: */
5262       case WhiteHSideCastleFR:
5263       case WhiteASideCastleFR:
5264       case BlackHSideCastleFR:
5265       case BlackASideCastleFR:
5266       /* End of code added by Tord */
5267       case IllegalMove:         /* bug or odd chess variant */
5268         *fromX = currentMoveString[0] - AAA;
5269         *fromY = currentMoveString[1] - ONE;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = currentMoveString[4];
5273         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5274             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5275     if (appData.debugMode) {
5276         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5277     }
5278             *fromX = *fromY = *toX = *toY = 0;
5279             return FALSE;
5280         }
5281         if (appData.testLegality) {
5282           return (*moveType != IllegalMove);
5283         } else {
5284           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5285                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5286         }
5287
5288       case WhiteDrop:
5289       case BlackDrop:
5290         *fromX = *moveType == WhiteDrop ?
5291           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5292           (int) CharToPiece(ToLower(currentMoveString[0]));
5293         *fromY = DROP_RANK;
5294         *toX = currentMoveString[2] - AAA;
5295         *toY = currentMoveString[3] - ONE;
5296         *promoChar = NULLCHAR;
5297         return TRUE;
5298
5299       case AmbiguousMove:
5300       case ImpossibleMove:
5301       case EndOfFile:
5302       case ElapsedTime:
5303       case Comment:
5304       case PGNTag:
5305       case NAG:
5306       case WhiteWins:
5307       case BlackWins:
5308       case GameIsDrawn:
5309       default:
5310     if (appData.debugMode) {
5311         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5312     }
5313         /* bug? */
5314         *fromX = *fromY = *toX = *toY = 0;
5315         *promoChar = NULLCHAR;
5316         return FALSE;
5317     }
5318 }
5319
5320 Boolean pushed = FALSE;
5321 char *lastParseAttempt;
5322
5323 void
5324 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5325 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5326   int fromX, fromY, toX, toY; char promoChar;
5327   ChessMove moveType;
5328   Boolean valid;
5329   int nr = 0;
5330
5331   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5332     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5333     pushed = TRUE;
5334   }
5335   endPV = forwardMostMove;
5336   do {
5337     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5338     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5339     lastParseAttempt = pv;
5340     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5341 if(appData.debugMode){
5342 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);
5343 }
5344     if(!valid && nr == 0 &&
5345        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5346         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5347         // Hande case where played move is different from leading PV move
5348         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5349         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5350         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5351         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5352           endPV += 2; // if position different, keep this
5353           moveList[endPV-1][0] = fromX + AAA;
5354           moveList[endPV-1][1] = fromY + ONE;
5355           moveList[endPV-1][2] = toX + AAA;
5356           moveList[endPV-1][3] = toY + ONE;
5357           parseList[endPV-1][0] = NULLCHAR;
5358           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5359         }
5360       }
5361     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5362     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5363     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5364     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5365         valid++; // allow comments in PV
5366         continue;
5367     }
5368     nr++;
5369     if(endPV+1 > framePtr) break; // no space, truncate
5370     if(!valid) break;
5371     endPV++;
5372     CopyBoard(boards[endPV], boards[endPV-1]);
5373     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5374     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5375     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5376     CoordsToAlgebraic(boards[endPV - 1],
5377                              PosFlags(endPV - 1),
5378                              fromY, fromX, toY, toX, promoChar,
5379                              parseList[endPV - 1]);
5380   } while(valid);
5381   if(atEnd == 2) return; // used hidden, for PV conversion
5382   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5383   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5384   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5385                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5386   DrawPosition(TRUE, boards[currentMove]);
5387 }
5388
5389 int
5390 MultiPV(ChessProgramState *cps)
5391 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5392         int i;
5393         for(i=0; i<cps->nrOptions; i++)
5394             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5395                 return i;
5396         return -1;
5397 }
5398
5399 Boolean
5400 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5401 {
5402         int startPV, multi, lineStart, origIndex = index;
5403         char *p, buf2[MSG_SIZ];
5404
5405         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5406         lastX = x; lastY = y;
5407         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5408         lineStart = startPV = index;
5409         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5410         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5411         index = startPV;
5412         do{ while(buf[index] && buf[index] != '\n') index++;
5413         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5414         buf[index] = 0;
5415         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5416                 int n = first.option[multi].value;
5417                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5418                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5419                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5420                 first.option[multi].value = n;
5421                 *start = *end = 0;
5422                 return FALSE;
5423         }
5424         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5425         *start = startPV; *end = index-1;
5426         return TRUE;
5427 }
5428
5429 char *
5430 PvToSAN(char *pv)
5431 {
5432         static char buf[10*MSG_SIZ];
5433         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5434         *buf = NULLCHAR;
5435         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5436         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5437         for(i = forwardMostMove; i<endPV; i++){
5438             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5439             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5440             k += strlen(buf+k);
5441         }
5442         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5443         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5444         endPV = savedEnd;
5445         return buf;
5446 }
5447
5448 Boolean
5449 LoadPV(int x, int y)
5450 { // called on right mouse click to load PV
5451   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5452   lastX = x; lastY = y;
5453   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5454   return TRUE;
5455 }
5456
5457 void
5458 UnLoadPV()
5459 {
5460   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5461   if(endPV < 0) return;
5462   endPV = -1;
5463   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5464         Boolean saveAnimate = appData.animate;
5465         if(pushed) {
5466             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5467                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5468             } else storedGames--; // abandon shelved tail of original game
5469         }
5470         pushed = FALSE;
5471         forwardMostMove = currentMove;
5472         currentMove = oldFMM;
5473         appData.animate = FALSE;
5474         ToNrEvent(forwardMostMove);
5475         appData.animate = saveAnimate;
5476   }
5477   currentMove = forwardMostMove;
5478   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5479   ClearPremoveHighlights();
5480   DrawPosition(TRUE, boards[currentMove]);
5481 }
5482
5483 void
5484 MovePV(int x, int y, int h)
5485 { // step through PV based on mouse coordinates (called on mouse move)
5486   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5487
5488   // we must somehow check if right button is still down (might be released off board!)
5489   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5490   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5491   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5492   if(!step) return;
5493   lastX = x; lastY = y;
5494
5495   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5496   if(endPV < 0) return;
5497   if(y < margin) step = 1; else
5498   if(y > h - margin) step = -1;
5499   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5500   currentMove += step;
5501   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5502   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5503                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5504   DrawPosition(FALSE, boards[currentMove]);
5505 }
5506
5507
5508 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5509 // All positions will have equal probability, but the current method will not provide a unique
5510 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5511 #define DARK 1
5512 #define LITE 2
5513 #define ANY 3
5514
5515 int squaresLeft[4];
5516 int piecesLeft[(int)BlackPawn];
5517 int seed, nrOfShuffles;
5518
5519 void GetPositionNumber()
5520 {       // sets global variable seed
5521         int i;
5522
5523         seed = appData.defaultFrcPosition;
5524         if(seed < 0) { // randomize based on time for negative FRC position numbers
5525                 for(i=0; i<50; i++) seed += random();
5526                 seed = random() ^ random() >> 8 ^ random() << 8;
5527                 if(seed<0) seed = -seed;
5528         }
5529 }
5530
5531 int put(Board board, int pieceType, int rank, int n, int shade)
5532 // put the piece on the (n-1)-th empty squares of the given shade
5533 {
5534         int i;
5535
5536         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5537                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5538                         board[rank][i] = (ChessSquare) pieceType;
5539                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5540                         squaresLeft[ANY]--;
5541                         piecesLeft[pieceType]--;
5542                         return i;
5543                 }
5544         }
5545         return -1;
5546 }
5547
5548
5549 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5550 // calculate where the next piece goes, (any empty square), and put it there
5551 {
5552         int i;
5553
5554         i = seed % squaresLeft[shade];
5555         nrOfShuffles *= squaresLeft[shade];
5556         seed /= squaresLeft[shade];
5557         put(board, pieceType, rank, i, shade);
5558 }
5559
5560 void AddTwoPieces(Board board, int pieceType, int rank)
5561 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5562 {
5563         int i, n=squaresLeft[ANY], j=n-1, k;
5564
5565         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5566         i = seed % k;  // pick one
5567         nrOfShuffles *= k;
5568         seed /= k;
5569         while(i >= j) i -= j--;
5570         j = n - 1 - j; i += j;
5571         put(board, pieceType, rank, j, ANY);
5572         put(board, pieceType, rank, i, ANY);
5573 }
5574
5575 void SetUpShuffle(Board board, int number)
5576 {
5577         int i, p, first=1;
5578
5579         GetPositionNumber(); nrOfShuffles = 1;
5580
5581         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5582         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5583         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5584
5585         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5586
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5588             p = (int) board[0][i];
5589             if(p < (int) BlackPawn) piecesLeft[p] ++;
5590             board[0][i] = EmptySquare;
5591         }
5592
5593         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5594             // shuffles restricted to allow normal castling put KRR first
5595             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5596                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5598                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5600                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5601             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5602                 put(board, WhiteRook, 0, 0, ANY);
5603             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5604         }
5605
5606         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5607             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5608             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5609                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5610                 while(piecesLeft[p] >= 2) {
5611                     AddOnePiece(board, p, 0, LITE);
5612                     AddOnePiece(board, p, 0, DARK);
5613                 }
5614                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5615             }
5616
5617         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5618             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5619             // but we leave King and Rooks for last, to possibly obey FRC restriction
5620             if(p == (int)WhiteRook) continue;
5621             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5622             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5623         }
5624
5625         // now everything is placed, except perhaps King (Unicorn) and Rooks
5626
5627         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5628             // Last King gets castling rights
5629             while(piecesLeft[(int)WhiteUnicorn]) {
5630                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634             while(piecesLeft[(int)WhiteKing]) {
5635                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5636                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5637             }
5638
5639
5640         } else {
5641             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5642             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5643         }
5644
5645         // Only Rooks can be left; simply place them all
5646         while(piecesLeft[(int)WhiteRook]) {
5647                 i = put(board, WhiteRook, 0, 0, ANY);
5648                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5649                         if(first) {
5650                                 first=0;
5651                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5652                         }
5653                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5654                 }
5655         }
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5657             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5658         }
5659
5660         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5661 }
5662
5663 int SetCharTable( char *table, const char * map )
5664 /* [HGM] moved here from winboard.c because of its general usefulness */
5665 /*       Basically a safe strcpy that uses the last character as King */
5666 {
5667     int result = FALSE; int NrPieces;
5668
5669     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5670                     && NrPieces >= 12 && !(NrPieces&1)) {
5671         int i; /* [HGM] Accept even length from 12 to 34 */
5672
5673         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5674         for( i=0; i<NrPieces/2-1; i++ ) {
5675             table[i] = map[i];
5676             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5677         }
5678         table[(int) WhiteKing]  = map[NrPieces/2-1];
5679         table[(int) BlackKing]  = map[NrPieces-1];
5680
5681         result = TRUE;
5682     }
5683
5684     return result;
5685 }
5686
5687 void Prelude(Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition(redraw)
5725      int redraw;
5726 {
5727     ChessSquare (* pieces)[BOARD_FILES];
5728     int i, j, pawnRow, overrule,
5729     oldx = gameInfo.boardWidth,
5730     oldy = gameInfo.boardHeight,
5731     oldh = gameInfo.holdingsWidth;
5732     static int oldv;
5733
5734     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5735
5736     /* [AS] Initialize pv info list [HGM] and game status */
5737     {
5738         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5739             pvInfoList[i].depth = 0;
5740             boards[i][EP_STATUS] = EP_NONE;
5741             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5742         }
5743
5744         initialRulePlies = 0; /* 50-move counter start */
5745
5746         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5747         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5748     }
5749
5750
5751     /* [HGM] logic here is completely changed. In stead of full positions */
5752     /* the initialized data only consist of the two backranks. The switch */
5753     /* selects which one we will use, which is than copied to the Board   */
5754     /* initialPosition, which for the rest is initialized by Pawns and    */
5755     /* empty squares. This initial position is then copied to boards[0],  */
5756     /* possibly after shuffling, so that it remains available.            */
5757
5758     gameInfo.holdingsWidth = 0; /* default board sizes */
5759     gameInfo.boardWidth    = 8;
5760     gameInfo.boardHeight   = 8;
5761     gameInfo.holdingsSize  = 0;
5762     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5763     for(i=0; i<BOARD_FILES-2; i++)
5764       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5765     initialPosition[EP_STATUS] = EP_NONE;
5766     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5767     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5768          SetCharTable(pieceNickName, appData.pieceNickNames);
5769     else SetCharTable(pieceNickName, "............");
5770     pieces = FIDEArray;
5771
5772     switch (gameInfo.variant) {
5773     case VariantFischeRandom:
5774       shuffleOpenings = TRUE;
5775     default:
5776       break;
5777     case VariantShatranj:
5778       pieces = ShatranjArray;
5779       nrCastlingRights = 0;
5780       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5781       break;
5782     case VariantMakruk:
5783       pieces = makrukArray;
5784       nrCastlingRights = 0;
5785       startedFromSetupPosition = TRUE;
5786       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5787       break;
5788     case VariantTwoKings:
5789       pieces = twoKingsArray;
5790       break;
5791     case VariantGrand:
5792       pieces = GrandArray;
5793       nrCastlingRights = 0;
5794       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5795       gameInfo.boardWidth = 10;
5796       gameInfo.boardHeight = 10;
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantCapaRandom:
5800       shuffleOpenings = TRUE;
5801     case VariantCapablanca:
5802       pieces = CapablancaArray;
5803       gameInfo.boardWidth = 10;
5804       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5805       break;
5806     case VariantGothic:
5807       pieces = GothicArray;
5808       gameInfo.boardWidth = 10;
5809       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5810       break;
5811     case VariantSChess:
5812       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5813       gameInfo.holdingsSize = 7;
5814       break;
5815     case VariantJanus:
5816       pieces = JanusArray;
5817       gameInfo.boardWidth = 10;
5818       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5819       nrCastlingRights = 6;
5820         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5821         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5822         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5823         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5824         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5825         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5826       break;
5827     case VariantFalcon:
5828       pieces = FalconArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5831       break;
5832     case VariantXiangqi:
5833       pieces = XiangqiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 10;
5836       nrCastlingRights = 0;
5837       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5838       break;
5839     case VariantShogi:
5840       pieces = ShogiArray;
5841       gameInfo.boardWidth  = 9;
5842       gameInfo.boardHeight = 9;
5843       gameInfo.holdingsSize = 7;
5844       nrCastlingRights = 0;
5845       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5846       break;
5847     case VariantCourier:
5848       pieces = CourierArray;
5849       gameInfo.boardWidth  = 12;
5850       nrCastlingRights = 0;
5851       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5852       break;
5853     case VariantKnightmate:
5854       pieces = KnightmateArray;
5855       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5856       break;
5857     case VariantSpartan:
5858       pieces = SpartanArray;
5859       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5860       break;
5861     case VariantFairy:
5862       pieces = fairyArray;
5863       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5864       break;
5865     case VariantGreat:
5866       pieces = GreatArray;
5867       gameInfo.boardWidth = 10;
5868       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5869       gameInfo.holdingsSize = 8;
5870       break;
5871     case VariantSuper:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5874       gameInfo.holdingsSize = 8;
5875       startedFromSetupPosition = TRUE;
5876       break;
5877     case VariantCrazyhouse:
5878     case VariantBughouse:
5879       pieces = FIDEArray;
5880       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5881       gameInfo.holdingsSize = 5;
5882       break;
5883     case VariantWildCastle:
5884       pieces = FIDEArray;
5885       /* !!?shuffle with kings guaranteed to be on d or e file */
5886       shuffleOpenings = 1;
5887       break;
5888     case VariantNoCastle:
5889       pieces = FIDEArray;
5890       nrCastlingRights = 0;
5891       /* !!?unconstrained back-rank shuffle */
5892       shuffleOpenings = 1;
5893       break;
5894     }
5895
5896     overrule = 0;
5897     if(appData.NrFiles >= 0) {
5898         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5899         gameInfo.boardWidth = appData.NrFiles;
5900     }
5901     if(appData.NrRanks >= 0) {
5902         gameInfo.boardHeight = appData.NrRanks;
5903     }
5904     if(appData.holdingsSize >= 0) {
5905         i = appData.holdingsSize;
5906         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5907         gameInfo.holdingsSize = i;
5908     }
5909     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5910     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5911         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5912
5913     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5914     if(pawnRow < 1) pawnRow = 1;
5915     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5916
5917     /* User pieceToChar list overrules defaults */
5918     if(appData.pieceToCharTable != NULL)
5919         SetCharTable(pieceToChar, appData.pieceToCharTable);
5920
5921     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5922
5923         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5924             s = (ChessSquare) 0; /* account holding counts in guard band */
5925         for( i=0; i<BOARD_HEIGHT; i++ )
5926             initialPosition[i][j] = s;
5927
5928         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5929         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5930         initialPosition[pawnRow][j] = WhitePawn;
5931         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5932         if(gameInfo.variant == VariantXiangqi) {
5933             if(j&1) {
5934                 initialPosition[pawnRow][j] =
5935                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5936                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5937                    initialPosition[2][j] = WhiteCannon;
5938                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5939                 }
5940             }
5941         }
5942         if(gameInfo.variant == VariantGrand) {
5943             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5944                initialPosition[0][j] = WhiteRook;
5945                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5946             }
5947         }
5948         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5949     }
5950     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5951
5952             j=BOARD_LEFT+1;
5953             initialPosition[1][j] = WhiteBishop;
5954             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5955             j=BOARD_RGHT-2;
5956             initialPosition[1][j] = WhiteRook;
5957             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5958     }
5959
5960     if( nrCastlingRights == -1) {
5961         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5962         /*       This sets default castling rights from none to normal corners   */
5963         /* Variants with other castling rights must set them themselves above    */
5964         nrCastlingRights = 6;
5965
5966         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5967         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5968         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5969         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5970         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5971         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5972      }
5973
5974      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5975      if(gameInfo.variant == VariantGreat) { // promotion commoners
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5977         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5979         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5980      }
5981      if( gameInfo.variant == VariantSChess ) {
5982       initialPosition[1][0] = BlackMarshall;
5983       initialPosition[2][0] = BlackAngel;
5984       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5985       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5986       initialPosition[1][1] = initialPosition[2][1] = 
5987       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5988      }
5989   if (appData.debugMode) {
5990     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5991   }
5992     if(shuffleOpenings) {
5993         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5994         startedFromSetupPosition = TRUE;
5995     }
5996     if(startedFromPositionFile) {
5997       /* [HGM] loadPos: use PositionFile for every new game */
5998       CopyBoard(initialPosition, filePosition);
5999       for(i=0; i<nrCastlingRights; i++)
6000           initialRights[i] = filePosition[CASTLING][i];
6001       startedFromSetupPosition = TRUE;
6002     }
6003
6004     CopyBoard(boards[0], initialPosition);
6005
6006     if(oldx != gameInfo.boardWidth ||
6007        oldy != gameInfo.boardHeight ||
6008        oldv != gameInfo.variant ||
6009        oldh != gameInfo.holdingsWidth
6010                                          )
6011             InitDrawingSizes(-2 ,0);
6012
6013     oldv = gameInfo.variant;
6014     if (redraw)
6015       DrawPosition(TRUE, boards[currentMove]);
6016 }
6017
6018 void
6019 SendBoard(cps, moveNum)
6020      ChessProgramState *cps;
6021      int moveNum;
6022 {
6023     char message[MSG_SIZ];
6024
6025     if (cps->useSetboard) {
6026       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6027       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6028       SendToProgram(message, cps);
6029       free(fen);
6030
6031     } else {
6032       ChessSquare *bp;
6033       int i, j;
6034       /* Kludge to set black to move, avoiding the troublesome and now
6035        * deprecated "black" command.
6036        */
6037       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6038         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6039
6040       SendToProgram("edit\n", cps);
6041       SendToProgram("#\n", cps);
6042       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6043         bp = &boards[moveNum][i][BOARD_LEFT];
6044         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6045           if ((int) *bp < (int) BlackPawn) {
6046             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6047                     AAA + j, ONE + i);
6048             if(message[0] == '+' || message[0] == '~') {
6049               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6050                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6051                         AAA + j, ONE + i);
6052             }
6053             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6054                 message[1] = BOARD_RGHT   - 1 - j + '1';
6055                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6056             }
6057             SendToProgram(message, cps);
6058           }
6059         }
6060       }
6061
6062       SendToProgram("c\n", cps);
6063       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6064         bp = &boards[moveNum][i][BOARD_LEFT];
6065         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6066           if (((int) *bp != (int) EmptySquare)
6067               && ((int) *bp >= (int) BlackPawn)) {
6068             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6069                     AAA + j, ONE + i);
6070             if(message[0] == '+' || message[0] == '~') {
6071               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6072                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6073                         AAA + j, ONE + i);
6074             }
6075             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6076                 message[1] = BOARD_RGHT   - 1 - j + '1';
6077                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6078             }
6079             SendToProgram(message, cps);
6080           }
6081         }
6082       }
6083
6084       SendToProgram(".\n", cps);
6085     }
6086     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6087 }
6088
6089 ChessSquare
6090 DefaultPromoChoice(int white)
6091 {
6092     ChessSquare result;
6093     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6094         result = WhiteFerz; // no choice
6095     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6096         result= WhiteKing; // in Suicide Q is the last thing we want
6097     else if(gameInfo.variant == VariantSpartan)
6098         result = white ? WhiteQueen : WhiteAngel;
6099     else result = WhiteQueen;
6100     if(!white) result = WHITE_TO_BLACK result;
6101     return result;
6102 }
6103
6104 static int autoQueen; // [HGM] oneclick
6105
6106 int
6107 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6108 {
6109     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6110     /* [HGM] add Shogi promotions */
6111     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6112     ChessSquare piece;
6113     ChessMove moveType;
6114     Boolean premove;
6115
6116     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6117     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6118
6119     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6120       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6121         return FALSE;
6122
6123     piece = boards[currentMove][fromY][fromX];
6124     if(gameInfo.variant == VariantShogi) {
6125         promotionZoneSize = BOARD_HEIGHT/3;
6126         highestPromotingPiece = (int)WhiteFerz;
6127     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6128         promotionZoneSize = 3;
6129     }
6130
6131     // Treat Lance as Pawn when it is not representing Amazon
6132     if(gameInfo.variant != VariantSuper) {
6133         if(piece == WhiteLance) piece = WhitePawn; else
6134         if(piece == BlackLance) piece = BlackPawn;
6135     }
6136
6137     // next weed out all moves that do not touch the promotion zone at all
6138     if((int)piece >= BlackPawn) {
6139         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6140              return FALSE;
6141         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6142     } else {
6143         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6144            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6145     }
6146
6147     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6148
6149     // weed out mandatory Shogi promotions
6150     if(gameInfo.variant == VariantShogi) {
6151         if(piece >= BlackPawn) {
6152             if(toY == 0 && piece == BlackPawn ||
6153                toY == 0 && piece == BlackQueen ||
6154                toY <= 1 && piece == BlackKnight) {
6155                 *promoChoice = '+';
6156                 return FALSE;
6157             }
6158         } else {
6159             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6160                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6161                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6162                 *promoChoice = '+';
6163                 return FALSE;
6164             }
6165         }
6166     }
6167
6168     // weed out obviously illegal Pawn moves
6169     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6170         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6171         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6172         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6173         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6174         // note we are not allowed to test for valid (non-)capture, due to premove
6175     }
6176
6177     // we either have a choice what to promote to, or (in Shogi) whether to promote
6178     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6179         *promoChoice = PieceToChar(BlackFerz);  // no choice
6180         return FALSE;
6181     }
6182     // no sense asking what we must promote to if it is going to explode...
6183     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6184         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6185         return FALSE;
6186     }
6187     // give caller the default choice even if we will not make it
6188     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6189     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6190     if(        sweepSelect && gameInfo.variant != VariantGreat
6191                            && gameInfo.variant != VariantGrand
6192                            && gameInfo.variant != VariantSuper) return FALSE;
6193     if(autoQueen) return FALSE; // predetermined
6194
6195     // suppress promotion popup on illegal moves that are not premoves
6196     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6197               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6198     if(appData.testLegality && !premove) {
6199         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6200                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6201         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6202             return FALSE;
6203     }
6204
6205     return TRUE;
6206 }
6207
6208 int
6209 InPalace(row, column)
6210      int row, column;
6211 {   /* [HGM] for Xiangqi */
6212     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6213          column < (BOARD_WIDTH + 4)/2 &&
6214          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6215     return FALSE;
6216 }
6217
6218 int
6219 PieceForSquare (x, y)
6220      int x;
6221      int y;
6222 {
6223   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6224      return -1;
6225   else
6226      return boards[currentMove][y][x];
6227 }
6228
6229 int
6230 OKToStartUserMove(x, y)
6231      int x, y;
6232 {
6233     ChessSquare from_piece;
6234     int white_piece;
6235
6236     if (matchMode) return FALSE;
6237     if (gameMode == EditPosition) return TRUE;
6238
6239     if (x >= 0 && y >= 0)
6240       from_piece = boards[currentMove][y][x];
6241     else
6242       from_piece = EmptySquare;
6243
6244     if (from_piece == EmptySquare) return FALSE;
6245
6246     white_piece = (int)from_piece >= (int)WhitePawn &&
6247       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6248
6249     switch (gameMode) {
6250       case AnalyzeFile:
6251       case TwoMachinesPlay:
6252       case EndOfGame:
6253         return FALSE;
6254
6255       case IcsObserving:
6256       case IcsIdle:
6257         return FALSE;
6258
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if (appData.zippyPlay) return FALSE;
6262         if (white_piece) {
6263             DisplayMoveError(_("You are playing Black"));
6264             return FALSE;
6265         }
6266         break;
6267
6268       case MachinePlaysBlack:
6269       case IcsPlayingWhite:
6270         if (appData.zippyPlay) return FALSE;
6271         if (!white_piece) {
6272             DisplayMoveError(_("You are playing White"));
6273             return FALSE;
6274         }
6275         break;
6276
6277       case PlayFromGameFile:
6278             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6279       case EditGame:
6280         if (!white_piece && WhiteOnMove(currentMove)) {
6281             DisplayMoveError(_("It is White's turn"));
6282             return FALSE;
6283         }
6284         if (white_piece && !WhiteOnMove(currentMove)) {
6285             DisplayMoveError(_("It is Black's turn"));
6286             return FALSE;
6287         }
6288         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6289             /* Editing correspondence game history */
6290             /* Could disallow this or prompt for confirmation */
6291             cmailOldMove = -1;
6292         }
6293         break;
6294
6295       case BeginningOfGame:
6296         if (appData.icsActive) return FALSE;
6297         if (!appData.noChessProgram) {
6298             if (!white_piece) {
6299                 DisplayMoveError(_("You are playing White"));
6300                 return FALSE;
6301             }
6302         }
6303         break;
6304
6305       case Training:
6306         if (!white_piece && WhiteOnMove(currentMove)) {
6307             DisplayMoveError(_("It is White's turn"));
6308             return FALSE;
6309         }
6310         if (white_piece && !WhiteOnMove(currentMove)) {
6311             DisplayMoveError(_("It is Black's turn"));
6312             return FALSE;
6313         }
6314         break;
6315
6316       default:
6317       case IcsExamining:
6318         break;
6319     }
6320     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6321         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6322         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6323         && gameMode != AnalyzeFile && gameMode != Training) {
6324         DisplayMoveError(_("Displayed position is not current"));
6325         return FALSE;
6326     }
6327     return TRUE;
6328 }
6329
6330 Boolean
6331 OnlyMove(int *x, int *y, Boolean captures) {
6332     DisambiguateClosure cl;
6333     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6334     switch(gameMode) {
6335       case MachinePlaysBlack:
6336       case IcsPlayingWhite:
6337       case BeginningOfGame:
6338         if(!WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case MachinePlaysWhite:
6341       case IcsPlayingBlack:
6342         if(WhiteOnMove(currentMove)) return FALSE;
6343         break;
6344       case EditGame:
6345         break;
6346       default:
6347         return FALSE;
6348     }
6349     cl.pieceIn = EmptySquare;
6350     cl.rfIn = *y;
6351     cl.ffIn = *x;
6352     cl.rtIn = -1;
6353     cl.ftIn = -1;
6354     cl.promoCharIn = NULLCHAR;
6355     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6356     if( cl.kind == NormalMove ||
6357         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6358         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6359         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6360       fromX = cl.ff;
6361       fromY = cl.rf;
6362       *x = cl.ft;
6363       *y = cl.rt;
6364       return TRUE;
6365     }
6366     if(cl.kind != ImpossibleMove) return FALSE;
6367     cl.pieceIn = EmptySquare;
6368     cl.rfIn = -1;
6369     cl.ffIn = -1;
6370     cl.rtIn = *y;
6371     cl.ftIn = *x;
6372     cl.promoCharIn = NULLCHAR;
6373     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6374     if( cl.kind == NormalMove ||
6375         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6376         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6377         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6378       fromX = cl.ff;
6379       fromY = cl.rf;
6380       *x = cl.ft;
6381       *y = cl.rt;
6382       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6383       return TRUE;
6384     }
6385     return FALSE;
6386 }
6387
6388 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6389 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6390 int lastLoadGameUseList = FALSE;
6391 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6392 ChessMove lastLoadGameStart = EndOfFile;
6393
6394 void
6395 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6396      int fromX, fromY, toX, toY;
6397      int promoChar;
6398 {
6399     ChessMove moveType;
6400     ChessSquare pdown, pup;
6401
6402     /* Check if the user is playing in turn.  This is complicated because we
6403        let the user "pick up" a piece before it is his turn.  So the piece he
6404        tried to pick up may have been captured by the time he puts it down!
6405        Therefore we use the color the user is supposed to be playing in this
6406        test, not the color of the piece that is currently on the starting
6407        square---except in EditGame mode, where the user is playing both
6408        sides; fortunately there the capture race can't happen.  (It can
6409        now happen in IcsExamining mode, but that's just too bad.  The user
6410        will get a somewhat confusing message in that case.)
6411        */
6412
6413     switch (gameMode) {
6414       case AnalyzeFile:
6415       case TwoMachinesPlay:
6416       case EndOfGame:
6417       case IcsObserving:
6418       case IcsIdle:
6419         /* We switched into a game mode where moves are not accepted,
6420            perhaps while the mouse button was down. */
6421         return;
6422
6423       case MachinePlaysWhite:
6424         /* User is moving for Black */
6425         if (WhiteOnMove(currentMove)) {
6426             DisplayMoveError(_("It is White's turn"));
6427             return;
6428         }
6429         break;
6430
6431       case MachinePlaysBlack:
6432         /* User is moving for White */
6433         if (!WhiteOnMove(currentMove)) {
6434             DisplayMoveError(_("It is Black's turn"));
6435             return;
6436         }
6437         break;
6438
6439       case PlayFromGameFile:
6440             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6441       case EditGame:
6442       case IcsExamining:
6443       case BeginningOfGame:
6444       case AnalyzeMode:
6445       case Training:
6446         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6447         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6448             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6449             /* User is moving for Black */
6450             if (WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is White's turn"));
6452                 return;
6453             }
6454         } else {
6455             /* User is moving for White */
6456             if (!WhiteOnMove(currentMove)) {
6457                 DisplayMoveError(_("It is Black's turn"));
6458                 return;
6459             }
6460         }
6461         break;
6462
6463       case IcsPlayingBlack:
6464         /* User is moving for Black */
6465         if (WhiteOnMove(currentMove)) {
6466             if (!appData.premove) {
6467                 DisplayMoveError(_("It is White's turn"));
6468             } else if (toX >= 0 && toY >= 0) {
6469                 premoveToX = toX;
6470                 premoveToY = toY;
6471                 premoveFromX = fromX;
6472                 premoveFromY = fromY;
6473                 premovePromoChar = promoChar;
6474                 gotPremove = 1;
6475                 if (appData.debugMode)
6476                     fprintf(debugFP, "Got premove: fromX %d,"
6477                             "fromY %d, toX %d, toY %d\n",
6478                             fromX, fromY, toX, toY);
6479             }
6480             return;
6481         }
6482         break;
6483
6484       case IcsPlayingWhite:
6485         /* User is moving for White */
6486         if (!WhiteOnMove(currentMove)) {
6487             if (!appData.premove) {
6488                 DisplayMoveError(_("It is Black's turn"));
6489             } else if (toX >= 0 && toY >= 0) {
6490                 premoveToX = toX;
6491                 premoveToY = toY;
6492                 premoveFromX = fromX;
6493                 premoveFromY = fromY;
6494                 premovePromoChar = promoChar;
6495                 gotPremove = 1;
6496                 if (appData.debugMode)
6497                     fprintf(debugFP, "Got premove: fromX %d,"
6498                             "fromY %d, toX %d, toY %d\n",
6499                             fromX, fromY, toX, toY);
6500             }
6501             return;
6502         }
6503         break;
6504
6505       default:
6506         break;
6507
6508       case EditPosition:
6509         /* EditPosition, empty square, or different color piece;
6510            click-click move is possible */
6511         if (toX == -2 || toY == -2) {
6512             boards[0][fromY][fromX] = EmptySquare;
6513             DrawPosition(FALSE, boards[currentMove]);
6514             return;
6515         } else if (toX >= 0 && toY >= 0) {
6516             boards[0][toY][toX] = boards[0][fromY][fromX];
6517             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6518                 if(boards[0][fromY][0] != EmptySquare) {
6519                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6520                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6521                 }
6522             } else
6523             if(fromX == BOARD_RGHT+1) {
6524                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6525                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6526                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6527                 }
6528             } else
6529             boards[0][fromY][fromX] = EmptySquare;
6530             DrawPosition(FALSE, boards[currentMove]);
6531             return;
6532         }
6533         return;
6534     }
6535
6536     if(toX < 0 || toY < 0) return;
6537     pdown = boards[currentMove][fromY][fromX];
6538     pup = boards[currentMove][toY][toX];
6539
6540     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6541     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6542          if( pup != EmptySquare ) return;
6543          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6544            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6545                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6546            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6547            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6548            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6549            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6550          fromY = DROP_RANK;
6551     }
6552
6553     /* [HGM] always test for legality, to get promotion info */
6554     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6555                                          fromY, fromX, toY, toX, promoChar);
6556
6557     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6558
6559     /* [HGM] but possibly ignore an IllegalMove result */
6560     if (appData.testLegality) {
6561         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6562             DisplayMoveError(_("Illegal move"));
6563             return;
6564         }
6565     }
6566
6567     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6568 }
6569
6570 /* Common tail of UserMoveEvent and DropMenuEvent */
6571 int
6572 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6573      ChessMove moveType;
6574      int fromX, fromY, toX, toY;
6575      /*char*/int promoChar;
6576 {
6577     char *bookHit = 0;
6578
6579     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6580         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6581         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6582         if(WhiteOnMove(currentMove)) {
6583             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6584         } else {
6585             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6586         }
6587     }
6588
6589     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6590        move type in caller when we know the move is a legal promotion */
6591     if(moveType == NormalMove && promoChar)
6592         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6593
6594     /* [HGM] <popupFix> The following if has been moved here from
6595        UserMoveEvent(). Because it seemed to belong here (why not allow
6596        piece drops in training games?), and because it can only be
6597        performed after it is known to what we promote. */
6598     if (gameMode == Training) {
6599       /* compare the move played on the board to the next move in the
6600        * game. If they match, display the move and the opponent's response.
6601        * If they don't match, display an error message.
6602        */
6603       int saveAnimate;
6604       Board testBoard;
6605       CopyBoard(testBoard, boards[currentMove]);
6606       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6607
6608       if (CompareBoards(testBoard, boards[currentMove+1])) {
6609         ForwardInner(currentMove+1);
6610
6611         /* Autoplay the opponent's response.
6612          * if appData.animate was TRUE when Training mode was entered,
6613          * the response will be animated.
6614          */
6615         saveAnimate = appData.animate;
6616         appData.animate = animateTraining;
6617         ForwardInner(currentMove+1);
6618         appData.animate = saveAnimate;
6619
6620         /* check for the end of the game */
6621         if (currentMove >= forwardMostMove) {
6622           gameMode = PlayFromGameFile;
6623           ModeHighlight();
6624           SetTrainingModeOff();
6625           DisplayInformation(_("End of game"));
6626         }
6627       } else {
6628         DisplayError(_("Incorrect move"), 0);
6629       }
6630       return 1;
6631     }
6632
6633   /* Ok, now we know that the move is good, so we can kill
6634      the previous line in Analysis Mode */
6635   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6636                                 && currentMove < forwardMostMove) {
6637     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6638     else forwardMostMove = currentMove;
6639   }
6640
6641   /* If we need the chess program but it's dead, restart it */
6642   ResurrectChessProgram();
6643
6644   /* A user move restarts a paused game*/
6645   if (pausing)
6646     PauseEvent();
6647
6648   thinkOutput[0] = NULLCHAR;
6649
6650   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6651
6652   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6653     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6654     return 1;
6655   }
6656
6657   if (gameMode == BeginningOfGame) {
6658     if (appData.noChessProgram) {
6659       gameMode = EditGame;
6660       SetGameInfo();
6661     } else {
6662       char buf[MSG_SIZ];
6663       gameMode = MachinePlaysBlack;
6664       StartClocks();
6665       SetGameInfo();
6666       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6667       DisplayTitle(buf);
6668       if (first.sendName) {
6669         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6670         SendToProgram(buf, &first);
6671       }
6672       StartClocks();
6673     }
6674     ModeHighlight();
6675   }
6676
6677   /* Relay move to ICS or chess engine */
6678   if (appData.icsActive) {
6679     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6680         gameMode == IcsExamining) {
6681       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6682         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6683         SendToICS("draw ");
6684         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6685       }
6686       // also send plain move, in case ICS does not understand atomic claims
6687       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6688       ics_user_moved = 1;
6689     }
6690   } else {
6691     if (first.sendTime && (gameMode == BeginningOfGame ||
6692                            gameMode == MachinePlaysWhite ||
6693                            gameMode == MachinePlaysBlack)) {
6694       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6695     }
6696     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6697          // [HGM] book: if program might be playing, let it use book
6698         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6699         first.maybeThinking = TRUE;
6700     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6701         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6702         SendBoard(&first, currentMove+1);
6703     } else SendMoveToProgram(forwardMostMove-1, &first);
6704     if (currentMove == cmailOldMove + 1) {
6705       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6706     }
6707   }
6708
6709   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710
6711   switch (gameMode) {
6712   case EditGame:
6713     if(appData.testLegality)
6714     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6715     case MT_NONE:
6716     case MT_CHECK:
6717       break;
6718     case MT_CHECKMATE:
6719     case MT_STAINMATE:
6720       if (WhiteOnMove(currentMove)) {
6721         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6722       } else {
6723         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6724       }
6725       break;
6726     case MT_STALEMATE:
6727       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6728       break;
6729     }
6730     break;
6731
6732   case MachinePlaysBlack:
6733   case MachinePlaysWhite:
6734     /* disable certain menu options while machine is thinking */
6735     SetMachineThinkingEnables();
6736     break;
6737
6738   default:
6739     break;
6740   }
6741
6742   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6743   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6744
6745   if(bookHit) { // [HGM] book: simulate book reply
6746         static char bookMove[MSG_SIZ]; // a bit generous?
6747
6748         programStats.nodes = programStats.depth = programStats.time =
6749         programStats.score = programStats.got_only_move = 0;
6750         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6751
6752         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6753         strcat(bookMove, bookHit);
6754         HandleMachineMove(bookMove, &first);
6755   }
6756   return 1;
6757 }
6758
6759 void
6760 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6761      Board board;
6762      int flags;
6763      ChessMove kind;
6764      int rf, ff, rt, ft;
6765      VOIDSTAR closure;
6766 {
6767     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6768     Markers *m = (Markers *) closure;
6769     if(rf == fromY && ff == fromX)
6770         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6771                          || kind == WhiteCapturesEnPassant
6772                          || kind == BlackCapturesEnPassant);
6773     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6774 }
6775
6776 void
6777 MarkTargetSquares(int clear)
6778 {
6779   int x, y;
6780   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6781      !appData.testLegality || gameMode == EditPosition) return;
6782   if(clear) {
6783     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6784   } else {
6785     int capt = 0;
6786     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6787     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6788       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6789       if(capt)
6790       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6791     }
6792   }
6793   DrawPosition(TRUE, NULL);
6794 }
6795
6796 int
6797 Explode(Board board, int fromX, int fromY, int toX, int toY)
6798 {
6799     if(gameInfo.variant == VariantAtomic &&
6800        (board[toY][toX] != EmptySquare ||                     // capture?
6801         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6802                          board[fromY][fromX] == BlackPawn   )
6803       )) {
6804         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6805         return TRUE;
6806     }
6807     return FALSE;
6808 }
6809
6810 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6811
6812 int CanPromote(ChessSquare piece, int y)
6813 {
6814         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6815         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6816         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6817            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6818            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6819                                                   gameInfo.variant == VariantMakruk) return FALSE;
6820         return (piece == BlackPawn && y == 1 ||
6821                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6822                 piece == BlackLance && y == 1 ||
6823                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6824 }
6825
6826 void LeftClick(ClickType clickType, int xPix, int yPix)
6827 {
6828     int x, y;
6829     Boolean saveAnimate;
6830     static int second = 0, promotionChoice = 0, clearFlag = 0;
6831     char promoChoice = NULLCHAR;
6832     ChessSquare piece;
6833
6834     if(appData.seekGraph && appData.icsActive && loggedOn &&
6835         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6836         SeekGraphClick(clickType, xPix, yPix, 0);
6837         return;
6838     }
6839
6840     if (clickType == Press) ErrorPopDown();
6841
6842     x = EventToSquare(xPix, BOARD_WIDTH);
6843     y = EventToSquare(yPix, BOARD_HEIGHT);
6844     if (!flipView && y >= 0) {
6845         y = BOARD_HEIGHT - 1 - y;
6846     }
6847     if (flipView && x >= 0) {
6848         x = BOARD_WIDTH - 1 - x;
6849     }
6850
6851     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6852         defaultPromoChoice = promoSweep;
6853         promoSweep = EmptySquare;   // terminate sweep
6854         promoDefaultAltered = TRUE;
6855         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6856     }
6857
6858     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6859         if(clickType == Release) return; // ignore upclick of click-click destination
6860         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6861         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6862         if(gameInfo.holdingsWidth &&
6863                 (WhiteOnMove(currentMove)
6864                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6865                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6866             // click in right holdings, for determining promotion piece
6867             ChessSquare p = boards[currentMove][y][x];
6868             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6869             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6870             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6871                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6872                 fromX = fromY = -1;
6873                 return;
6874             }
6875         }
6876         DrawPosition(FALSE, boards[currentMove]);
6877         return;
6878     }
6879
6880     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6881     if(clickType == Press
6882             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6883               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6884               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6885         return;
6886
6887     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6888         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6889
6890     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6891         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6892                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6893         defaultPromoChoice = DefaultPromoChoice(side);
6894     }
6895
6896     autoQueen = appData.alwaysPromoteToQueen;
6897
6898     if (fromX == -1) {
6899       int originalY = y;
6900       gatingPiece = EmptySquare;
6901       if (clickType != Press) {
6902         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6903             DragPieceEnd(xPix, yPix); dragging = 0;
6904             DrawPosition(FALSE, NULL);
6905         }
6906         return;
6907       }
6908       fromX = x; fromY = y; toX = toY = -1;
6909       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6910          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6911          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6912             /* First square */
6913             if (OKToStartUserMove(fromX, fromY)) {
6914                 second = 0;
6915                 MarkTargetSquares(0);
6916                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6917                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6918                     promoSweep = defaultPromoChoice;
6919                     selectFlag = 0; lastX = xPix; lastY = yPix;
6920                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6921                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6922                 }
6923                 if (appData.highlightDragging) {
6924                     SetHighlights(fromX, fromY, -1, -1);
6925                 }
6926             } else fromX = fromY = -1;
6927             return;
6928         }
6929     }
6930
6931     /* fromX != -1 */
6932     if (clickType == Press && gameMode != EditPosition) {
6933         ChessSquare fromP;
6934         ChessSquare toP;
6935         int frc;
6936
6937         // ignore off-board to clicks
6938         if(y < 0 || x < 0) return;
6939
6940         /* Check if clicking again on the same color piece */
6941         fromP = boards[currentMove][fromY][fromX];
6942         toP = boards[currentMove][y][x];
6943         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6944         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6945              WhitePawn <= toP && toP <= WhiteKing &&
6946              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6947              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6948             (BlackPawn <= fromP && fromP <= BlackKing &&
6949              BlackPawn <= toP && toP <= BlackKing &&
6950              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6951              !(fromP == BlackKing && toP == BlackRook && frc))) {
6952             /* Clicked again on same color piece -- changed his mind */
6953             second = (x == fromX && y == fromY);
6954             promoDefaultAltered = FALSE;
6955             MarkTargetSquares(1);
6956            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6957             if (appData.highlightDragging) {
6958                 SetHighlights(x, y, -1, -1);
6959             } else {
6960                 ClearHighlights();
6961             }
6962             if (OKToStartUserMove(x, y)) {
6963                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6964                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6965                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6966                  gatingPiece = boards[currentMove][fromY][fromX];
6967                 else gatingPiece = EmptySquare;
6968                 fromX = x;
6969                 fromY = y; dragging = 1;
6970                 MarkTargetSquares(0);
6971                 DragPieceBegin(xPix, yPix, FALSE);
6972                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6973                     promoSweep = defaultPromoChoice;
6974                     selectFlag = 0; lastX = xPix; lastY = yPix;
6975                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6976                 }
6977             }
6978            }
6979            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6980            second = FALSE; 
6981         }
6982         // ignore clicks on holdings
6983         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6984     }
6985
6986     if (clickType == Release && x == fromX && y == fromY) {
6987         DragPieceEnd(xPix, yPix); dragging = 0;
6988         if(clearFlag) {
6989             // a deferred attempt to click-click move an empty square on top of a piece
6990             boards[currentMove][y][x] = EmptySquare;
6991             ClearHighlights();
6992             DrawPosition(FALSE, boards[currentMove]);
6993             fromX = fromY = -1; clearFlag = 0;
6994             return;
6995         }
6996         if (appData.animateDragging) {
6997             /* Undo animation damage if any */
6998             DrawPosition(FALSE, NULL);
6999         }
7000         if (second) {
7001             /* Second up/down in same square; just abort move */
7002             second = 0;
7003             fromX = fromY = -1;
7004             gatingPiece = EmptySquare;
7005             ClearHighlights();
7006             gotPremove = 0;
7007             ClearPremoveHighlights();
7008         } else {
7009             /* First upclick in same square; start click-click mode */
7010             SetHighlights(x, y, -1, -1);
7011         }
7012         return;
7013     }
7014
7015     clearFlag = 0;
7016
7017     /* we now have a different from- and (possibly off-board) to-square */
7018     /* Completed move */
7019     toX = x;
7020     toY = y;
7021     saveAnimate = appData.animate;
7022     MarkTargetSquares(1);
7023     if (clickType == Press) {
7024         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7025             // must be Edit Position mode with empty-square selected
7026             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7027             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7028             return;
7029         }
7030         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7031             ChessSquare piece = boards[currentMove][fromY][fromX];
7032             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7033             promoSweep = defaultPromoChoice;
7034             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7035             selectFlag = 0; lastX = xPix; lastY = yPix;
7036             Sweep(0); // Pawn that is going to promote: preview promotion piece
7037             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7038             DrawPosition(FALSE, boards[currentMove]);
7039             return;
7040         }
7041         /* Finish clickclick move */
7042         if (appData.animate || appData.highlightLastMove) {
7043             SetHighlights(fromX, fromY, toX, toY);
7044         } else {
7045             ClearHighlights();
7046         }
7047     } else {
7048         /* Finish drag move */
7049         if (appData.highlightLastMove) {
7050             SetHighlights(fromX, fromY, toX, toY);
7051         } else {
7052             ClearHighlights();
7053         }
7054         DragPieceEnd(xPix, yPix); dragging = 0;
7055         /* Don't animate move and drag both */
7056         appData.animate = FALSE;
7057     }
7058
7059     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7060     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7061         ChessSquare piece = boards[currentMove][fromY][fromX];
7062         if(gameMode == EditPosition && piece != EmptySquare &&
7063            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7064             int n;
7065
7066             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7067                 n = PieceToNumber(piece - (int)BlackPawn);
7068                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7069                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7070                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7071             } else
7072             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7073                 n = PieceToNumber(piece);
7074                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7075                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7076                 boards[currentMove][n][BOARD_WIDTH-2]++;
7077             }
7078             boards[currentMove][fromY][fromX] = EmptySquare;
7079         }
7080         ClearHighlights();
7081         fromX = fromY = -1;
7082         DrawPosition(TRUE, boards[currentMove]);
7083         return;
7084     }
7085
7086     // off-board moves should not be highlighted
7087     if(x < 0 || y < 0) ClearHighlights();
7088
7089     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7090
7091     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7092         SetHighlights(fromX, fromY, toX, toY);
7093         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7094             // [HGM] super: promotion to captured piece selected from holdings
7095             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7096             promotionChoice = TRUE;
7097             // kludge follows to temporarily execute move on display, without promoting yet
7098             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7099             boards[currentMove][toY][toX] = p;
7100             DrawPosition(FALSE, boards[currentMove]);
7101             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7102             boards[currentMove][toY][toX] = q;
7103             DisplayMessage("Click in holdings to choose piece", "");
7104             return;
7105         }
7106         PromotionPopUp();
7107     } else {
7108         int oldMove = currentMove;
7109         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7110         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7111         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7112         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7113            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7114             DrawPosition(TRUE, boards[currentMove]);
7115         fromX = fromY = -1;
7116     }
7117     appData.animate = saveAnimate;
7118     if (appData.animate || appData.animateDragging) {
7119         /* Undo animation damage if needed */
7120         DrawPosition(FALSE, NULL);
7121     }
7122 }
7123
7124 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7125 {   // front-end-free part taken out of PieceMenuPopup
7126     int whichMenu; int xSqr, ySqr;
7127
7128     if(seekGraphUp) { // [HGM] seekgraph
7129         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7130         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7131         return -2;
7132     }
7133
7134     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7135          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7136         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7137         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7138         if(action == Press)   {
7139             originalFlip = flipView;
7140             flipView = !flipView; // temporarily flip board to see game from partners perspective
7141             DrawPosition(TRUE, partnerBoard);
7142             DisplayMessage(partnerStatus, "");
7143             partnerUp = TRUE;
7144         } else if(action == Release) {
7145             flipView = originalFlip;
7146             DrawPosition(TRUE, boards[currentMove]);
7147             partnerUp = FALSE;
7148         }
7149         return -2;
7150     }
7151
7152     xSqr = EventToSquare(x, BOARD_WIDTH);
7153     ySqr = EventToSquare(y, BOARD_HEIGHT);
7154     if (action == Release) {
7155         if(pieceSweep != EmptySquare) {
7156             EditPositionMenuEvent(pieceSweep, toX, toY);
7157             pieceSweep = EmptySquare;
7158         } else UnLoadPV(); // [HGM] pv
7159     }
7160     if (action != Press) return -2; // return code to be ignored
7161     switch (gameMode) {
7162       case IcsExamining:
7163         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7164       case EditPosition:
7165         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7166         if (xSqr < 0 || ySqr < 0) return -1;
7167         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7168         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7169         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7170         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7171         NextPiece(0);
7172         return 2; // grab
7173       case IcsObserving:
7174         if(!appData.icsEngineAnalyze) return -1;
7175       case IcsPlayingWhite:
7176       case IcsPlayingBlack:
7177         if(!appData.zippyPlay) goto noZip;
7178       case AnalyzeMode:
7179       case AnalyzeFile:
7180       case MachinePlaysWhite:
7181       case MachinePlaysBlack:
7182       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7183         if (!appData.dropMenu) {
7184           LoadPV(x, y);
7185           return 2; // flag front-end to grab mouse events
7186         }
7187         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7188            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7189       case EditGame:
7190       noZip:
7191         if (xSqr < 0 || ySqr < 0) return -1;
7192         if (!appData.dropMenu || appData.testLegality &&
7193             gameInfo.variant != VariantBughouse &&
7194             gameInfo.variant != VariantCrazyhouse) return -1;
7195         whichMenu = 1; // drop menu
7196         break;
7197       default:
7198         return -1;
7199     }
7200
7201     if (((*fromX = xSqr) < 0) ||
7202         ((*fromY = ySqr) < 0)) {
7203         *fromX = *fromY = -1;
7204         return -1;
7205     }
7206     if (flipView)
7207       *fromX = BOARD_WIDTH - 1 - *fromX;
7208     else
7209       *fromY = BOARD_HEIGHT - 1 - *fromY;
7210
7211     return whichMenu;
7212 }
7213
7214 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7215 {
7216 //    char * hint = lastHint;
7217     FrontEndProgramStats stats;
7218
7219     stats.which = cps == &first ? 0 : 1;
7220     stats.depth = cpstats->depth;
7221     stats.nodes = cpstats->nodes;
7222     stats.score = cpstats->score;
7223     stats.time = cpstats->time;
7224     stats.pv = cpstats->movelist;
7225     stats.hint = lastHint;
7226     stats.an_move_index = 0;
7227     stats.an_move_count = 0;
7228
7229     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7230         stats.hint = cpstats->move_name;
7231         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7232         stats.an_move_count = cpstats->nr_moves;
7233     }
7234
7235     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
7236
7237     SetProgramStats( &stats );
7238 }
7239
7240 void
7241 ClearEngineOutputPane(int which)
7242 {
7243     static FrontEndProgramStats dummyStats;
7244     dummyStats.which = which;
7245     dummyStats.pv = "#";
7246     SetProgramStats( &dummyStats );
7247 }
7248
7249 #define MAXPLAYERS 500
7250
7251 char *
7252 TourneyStandings(int display)
7253 {
7254     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7255     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7256     char result, *p, *names[MAXPLAYERS];
7257
7258     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7259         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7260     names[0] = p = strdup(appData.participants);
7261     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7262
7263     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7264
7265     while(result = appData.results[nr]) {
7266         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7267         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7268         wScore = bScore = 0;
7269         switch(result) {
7270           case '+': wScore = 2; break;
7271           case '-': bScore = 2; break;
7272           case '=': wScore = bScore = 1; break;
7273           case ' ':
7274           case '*': return strdup("busy"); // tourney not finished
7275         }
7276         score[w] += wScore;
7277         score[b] += bScore;
7278         games[w]++;
7279         games[b]++;
7280         nr++;
7281     }
7282     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7283     for(w=0; w<nPlayers; w++) {
7284         bScore = -1;
7285         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7286         ranking[w] = b; points[w] = bScore; score[b] = -2;
7287     }
7288     p = malloc(nPlayers*34+1);
7289     for(w=0; w<nPlayers && w<display; w++)
7290         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7291     free(names[0]);
7292     return p;
7293 }
7294
7295 void
7296 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7297 {       // count all piece types
7298         int p, f, r;
7299         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7300         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7301         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7302                 p = board[r][f];
7303                 pCnt[p]++;
7304                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7305                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7306                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7307                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7308                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7309                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7310         }
7311 }
7312
7313 int
7314 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7315 {
7316         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7317         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7318
7319         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7320         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7321         if(myPawns == 2 && nMine == 3) // KPP
7322             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7323         if(myPawns == 1 && nMine == 2) // KP
7324             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7325         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7326             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7327         if(myPawns) return FALSE;
7328         if(pCnt[WhiteRook+side])
7329             return pCnt[BlackRook-side] ||
7330                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7331                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7332                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7333         if(pCnt[WhiteCannon+side]) {
7334             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7335             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7336         }
7337         if(pCnt[WhiteKnight+side])
7338             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7339         return FALSE;
7340 }
7341
7342 int
7343 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7344 {
7345         VariantClass v = gameInfo.variant;
7346
7347         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7348         if(v == VariantShatranj) return TRUE; // always winnable through baring
7349         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7350         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7351
7352         if(v == VariantXiangqi) {
7353                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7354
7355                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7356                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7357                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7358                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7359                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7360                 if(stale) // we have at least one last-rank P plus perhaps C
7361                     return majors // KPKX
7362                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7363                 else // KCA*E*
7364                     return pCnt[WhiteFerz+side] // KCAK
7365                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7366                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7367                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7368
7369         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7370                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7371
7372                 if(nMine == 1) return FALSE; // bare King
7373                 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
7374                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7375                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7376                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7377                 if(pCnt[WhiteKnight+side])
7378                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7379                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7380                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7381                 if(nBishops)
7382                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7383                 if(pCnt[WhiteAlfil+side])
7384                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7385                 if(pCnt[WhiteWazir+side])
7386                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7387         }
7388
7389         return TRUE;
7390 }
7391
7392 int
7393 CompareWithRights(Board b1, Board b2)
7394 {
7395     int rights = 0;
7396     if(!CompareBoards(b1, b2)) return FALSE;
7397     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7398     /* compare castling rights */
7399     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7400            rights++; /* King lost rights, while rook still had them */
7401     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7402         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7403            rights++; /* but at least one rook lost them */
7404     }
7405     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7406            rights++;
7407     if( b1[CASTLING][5] != NoRights ) {
7408         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7409            rights++;
7410     }
7411     return rights == 0;
7412 }
7413
7414 int
7415 Adjudicate(ChessProgramState *cps)
7416 {       // [HGM] some adjudications useful with buggy engines
7417         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7418         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7419         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7420         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7421         int k, count = 0; static int bare = 1;
7422         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7423         Boolean canAdjudicate = !appData.icsActive;
7424
7425         // most tests only when we understand the game, i.e. legality-checking on
7426             if( appData.testLegality )
7427             {   /* [HGM] Some more adjudications for obstinate engines */
7428                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7429                 static int moveCount = 6;
7430                 ChessMove result;
7431                 char *reason = NULL;
7432
7433                 /* Count what is on board. */
7434                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7435
7436                 /* Some material-based adjudications that have to be made before stalemate test */
7437                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7438                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7439                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7440                      if(canAdjudicate && appData.checkMates) {
7441                          if(engineOpponent)
7442                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7443                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7444                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7445                          return 1;
7446                      }
7447                 }
7448
7449                 /* Bare King in Shatranj (loses) or Losers (wins) */
7450                 if( nrW == 1 || nrB == 1) {
7451                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7452                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7453                      if(canAdjudicate && appData.checkMates) {
7454                          if(engineOpponent)
7455                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7456                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7457                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7458                          return 1;
7459                      }
7460                   } else
7461                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7462                   {    /* bare King */
7463                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7464                         if(canAdjudicate && appData.checkMates) {
7465                             /* but only adjudicate if adjudication enabled */
7466                             if(engineOpponent)
7467                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7468                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7469                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7470                             return 1;
7471                         }
7472                   }
7473                 } else bare = 1;
7474
7475
7476             // don't wait for engine to announce game end if we can judge ourselves
7477             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7478               case MT_CHECK:
7479                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7480                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7481                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7482                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7483                             checkCnt++;
7484                         if(checkCnt >= 2) {
7485                             reason = "Xboard adjudication: 3rd check";
7486                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7487                             break;
7488                         }
7489                     }
7490                 }
7491               case MT_NONE:
7492               default:
7493                 break;
7494               case MT_STALEMATE:
7495               case MT_STAINMATE:
7496                 reason = "Xboard adjudication: Stalemate";
7497                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7498                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7499                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7500                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7501                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7502                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7503                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7504                                                                         EP_CHECKMATE : EP_WINS);
7505                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7506                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7507                 }
7508                 break;
7509               case MT_CHECKMATE:
7510                 reason = "Xboard adjudication: Checkmate";
7511                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7512                 break;
7513             }
7514
7515                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7516                     case EP_STALEMATE:
7517                         result = GameIsDrawn; break;
7518                     case EP_CHECKMATE:
7519                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7520                     case EP_WINS:
7521                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7522                     default:
7523                         result = EndOfFile;
7524                 }
7525                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7526                     if(engineOpponent)
7527                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7528                     GameEnds( result, reason, GE_XBOARD );
7529                     return 1;
7530                 }
7531
7532                 /* Next absolutely insufficient mating material. */
7533                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7534                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7535                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7536
7537                      /* always flag draws, for judging claims */
7538                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7539
7540                      if(canAdjudicate && appData.materialDraws) {
7541                          /* but only adjudicate them if adjudication enabled */
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7547                          return 1;
7548                      }
7549                 }
7550
7551                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7552                 if(gameInfo.variant == VariantXiangqi ?
7553                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7554                  : nrW + nrB == 4 &&
7555                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7556                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7557                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7558                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7559                    ) ) {
7560                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7561                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7562                           if(engineOpponent) {
7563                             SendToProgram("force\n", engineOpponent); // suppress reply
7564                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7565                           }
7566                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7567                           return 1;
7568                      }
7569                 } else moveCount = 6;
7570             }
7571         if (appData.debugMode) { int i;
7572             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7573                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7574                     appData.drawRepeats);
7575             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7576               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7577
7578         }
7579
7580         // Repetition draws and 50-move rule can be applied independently of legality testing
7581
7582                 /* Check for rep-draws */
7583                 count = 0;
7584                 for(k = forwardMostMove-2;
7585                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7586                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7587                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7588                     k-=2)
7589                 {   int rights=0;
7590                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7591                         /* compare castling rights */
7592                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7593                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7594                                 rights++; /* King lost rights, while rook still had them */
7595                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7596                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7597                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7598                                    rights++; /* but at least one rook lost them */
7599                         }
7600                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7601                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7602                                 rights++;
7603                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7604                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7605                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7606                                    rights++;
7607                         }
7608                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7609                             && appData.drawRepeats > 1) {
7610                              /* adjudicate after user-specified nr of repeats */
7611                              int result = GameIsDrawn;
7612                              char *details = "XBoard adjudication: repetition draw";
7613                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7614                                 // [HGM] xiangqi: check for forbidden perpetuals
7615                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7616                                 for(m=forwardMostMove; m>k; m-=2) {
7617                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7618                                         ourPerpetual = 0; // the current mover did not always check
7619                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7620                                         hisPerpetual = 0; // the opponent did not always check
7621                                 }
7622                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7623                                                                         ourPerpetual, hisPerpetual);
7624                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7625                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7626                                     details = "Xboard adjudication: perpetual checking";
7627                                 } else
7628                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7629                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7630                                 } else
7631                                 // Now check for perpetual chases
7632                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7633                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7634                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7635                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7636                                         static char resdet[MSG_SIZ];
7637                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7638                                         details = resdet;
7639                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7640                                     } else
7641                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7642                                         break; // Abort repetition-checking loop.
7643                                 }
7644                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7645                              }
7646                              if(engineOpponent) {
7647                                SendToProgram("force\n", engineOpponent); // suppress reply
7648                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7649                              }
7650                              GameEnds( result, details, GE_XBOARD );
7651                              return 1;
7652                         }
7653                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7654                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7655                     }
7656                 }
7657
7658                 /* Now we test for 50-move draws. Determine ply count */
7659                 count = forwardMostMove;
7660                 /* look for last irreversble move */
7661                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7662                     count--;
7663                 /* if we hit starting position, add initial plies */
7664                 if( count == backwardMostMove )
7665                     count -= initialRulePlies;
7666                 count = forwardMostMove - count;
7667                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7668                         // adjust reversible move counter for checks in Xiangqi
7669                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7670                         if(i < backwardMostMove) i = backwardMostMove;
7671                         while(i <= forwardMostMove) {
7672                                 lastCheck = inCheck; // check evasion does not count
7673                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7674                                 if(inCheck || lastCheck) count--; // check does not count
7675                                 i++;
7676                         }
7677                 }
7678                 if( count >= 100)
7679                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7680                          /* this is used to judge if draw claims are legal */
7681                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7682                          if(engineOpponent) {
7683                            SendToProgram("force\n", engineOpponent); // suppress reply
7684                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7685                          }
7686                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7687                          return 1;
7688                 }
7689
7690                 /* if draw offer is pending, treat it as a draw claim
7691                  * when draw condition present, to allow engines a way to
7692                  * claim draws before making their move to avoid a race
7693                  * condition occurring after their move
7694                  */
7695                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7696                          char *p = NULL;
7697                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7698                              p = "Draw claim: 50-move rule";
7699                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7700                              p = "Draw claim: 3-fold repetition";
7701                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7702                              p = "Draw claim: insufficient mating material";
7703                          if( p != NULL && canAdjudicate) {
7704                              if(engineOpponent) {
7705                                SendToProgram("force\n", engineOpponent); // suppress reply
7706                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7707                              }
7708                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7709                              return 1;
7710                          }
7711                 }
7712
7713                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7714                     if(engineOpponent) {
7715                       SendToProgram("force\n", engineOpponent); // suppress reply
7716                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7717                     }
7718                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7719                     return 1;
7720                 }
7721         return 0;
7722 }
7723
7724 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7725 {   // [HGM] book: this routine intercepts moves to simulate book replies
7726     char *bookHit = NULL;
7727
7728     //first determine if the incoming move brings opponent into his book
7729     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7730         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7731     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7732     if(bookHit != NULL && !cps->bookSuspend) {
7733         // make sure opponent is not going to reply after receiving move to book position
7734         SendToProgram("force\n", cps);
7735         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7736     }
7737     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7738     // now arrange restart after book miss
7739     if(bookHit) {
7740         // after a book hit we never send 'go', and the code after the call to this routine
7741         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7742         char buf[MSG_SIZ], *move = bookHit;
7743         if(cps->useSAN) {
7744             int fromX, fromY, toX, toY;
7745             char promoChar;
7746             ChessMove moveType;
7747             move = buf + 30;
7748             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7749                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7750                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7751                                     PosFlags(forwardMostMove),
7752                                     fromY, fromX, toY, toX, promoChar, move);
7753             } else {
7754                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7755                 bookHit = NULL;
7756             }
7757         }
7758         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7759         SendToProgram(buf, cps);
7760         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7761     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7762         SendToProgram("go\n", cps);
7763         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7764     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7765         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7766             SendToProgram("go\n", cps);
7767         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7768     }
7769     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7770 }
7771
7772 char *savedMessage;
7773 ChessProgramState *savedState;
7774 void DeferredBookMove(void)
7775 {
7776         if(savedState->lastPing != savedState->lastPong)
7777                     ScheduleDelayedEvent(DeferredBookMove, 10);
7778         else
7779         HandleMachineMove(savedMessage, savedState);
7780 }
7781
7782 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7783
7784 void
7785 HandleMachineMove(message, cps)
7786      char *message;
7787      ChessProgramState *cps;
7788 {
7789     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7790     char realname[MSG_SIZ];
7791     int fromX, fromY, toX, toY;
7792     ChessMove moveType;
7793     char promoChar;
7794     char *p, *pv=buf1;
7795     int machineWhite;
7796     char *bookHit;
7797
7798     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7799         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7800         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7801             DisplayError(_("Invalid pairing from pairing engine"), 0);
7802             return;
7803         }
7804         pairingReceived = 1;
7805         NextMatchGame();
7806         return; // Skim the pairing messages here.
7807     }
7808
7809     cps->userError = 0;
7810
7811 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7812     /*
7813      * Kludge to ignore BEL characters
7814      */
7815     while (*message == '\007') message++;
7816
7817     /*
7818      * [HGM] engine debug message: ignore lines starting with '#' character
7819      */
7820     if(cps->debug && *message == '#') return;
7821
7822     /*
7823      * Look for book output
7824      */
7825     if (cps == &first && bookRequested) {
7826         if (message[0] == '\t' || message[0] == ' ') {
7827             /* Part of the book output is here; append it */
7828             strcat(bookOutput, message);
7829             strcat(bookOutput, "  \n");
7830             return;
7831         } else if (bookOutput[0] != NULLCHAR) {
7832             /* All of book output has arrived; display it */
7833             char *p = bookOutput;
7834             while (*p != NULLCHAR) {
7835                 if (*p == '\t') *p = ' ';
7836                 p++;
7837             }
7838             DisplayInformation(bookOutput);
7839             bookRequested = FALSE;
7840             /* Fall through to parse the current output */
7841         }
7842     }
7843
7844     /*
7845      * Look for machine move.
7846      */
7847     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7848         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7849     {
7850         /* This method is only useful on engines that support ping */
7851         if (cps->lastPing != cps->lastPong) {
7852           if (gameMode == BeginningOfGame) {
7853             /* Extra move from before last new; ignore */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7856             }
7857           } else {
7858             if (appData.debugMode) {
7859                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7860                         cps->which, gameMode);
7861             }
7862
7863             SendToProgram("undo\n", cps);
7864           }
7865           return;
7866         }
7867
7868         switch (gameMode) {
7869           case BeginningOfGame:
7870             /* Extra move from before last reset; ignore */
7871             if (appData.debugMode) {
7872                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7873             }
7874             return;
7875
7876           case EndOfGame:
7877           case IcsIdle:
7878           default:
7879             /* Extra move after we tried to stop.  The mode test is
7880                not a reliable way of detecting this problem, but it's
7881                the best we can do on engines that don't support ping.
7882             */
7883             if (appData.debugMode) {
7884                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7885                         cps->which, gameMode);
7886             }
7887             SendToProgram("undo\n", cps);
7888             return;
7889
7890           case MachinePlaysWhite:
7891           case IcsPlayingWhite:
7892             machineWhite = TRUE;
7893             break;
7894
7895           case MachinePlaysBlack:
7896           case IcsPlayingBlack:
7897             machineWhite = FALSE;
7898             break;
7899
7900           case TwoMachinesPlay:
7901             machineWhite = (cps->twoMachinesColor[0] == 'w');
7902             break;
7903         }
7904         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7905             if (appData.debugMode) {
7906                 fprintf(debugFP,
7907                         "Ignoring move out of turn by %s, gameMode %d"
7908                         ", forwardMost %d\n",
7909                         cps->which, gameMode, forwardMostMove);
7910             }
7911             return;
7912         }
7913
7914     if (appData.debugMode) { int f = forwardMostMove;
7915         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7916                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7917                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7918     }
7919         if(cps->alphaRank) AlphaRank(machineMove, 4);
7920         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7921                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7922             /* Machine move could not be parsed; ignore it. */
7923           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7924                     machineMove, _(cps->which));
7925             DisplayError(buf1, 0);
7926             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7927                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7928             if (gameMode == TwoMachinesPlay) {
7929               GameEnds(machineWhite ? BlackWins : WhiteWins,
7930                        buf1, GE_XBOARD);
7931             }
7932             return;
7933         }
7934
7935         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7936         /* So we have to redo legality test with true e.p. status here,  */
7937         /* to make sure an illegal e.p. capture does not slip through,   */
7938         /* to cause a forfeit on a justified illegal-move complaint      */
7939         /* of the opponent.                                              */
7940         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7941            ChessMove moveType;
7942            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7943                              fromY, fromX, toY, toX, promoChar);
7944             if (appData.debugMode) {
7945                 int i;
7946                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7947                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7948                 fprintf(debugFP, "castling rights\n");
7949             }
7950             if(moveType == IllegalMove) {
7951               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7952                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7953                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7954                            buf1, GE_XBOARD);
7955                 return;
7956            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7957            /* [HGM] Kludge to handle engines that send FRC-style castling
7958               when they shouldn't (like TSCP-Gothic) */
7959            switch(moveType) {
7960              case WhiteASideCastleFR:
7961              case BlackASideCastleFR:
7962                toX+=2;
7963                currentMoveString[2]++;
7964                break;
7965              case WhiteHSideCastleFR:
7966              case BlackHSideCastleFR:
7967                toX--;
7968                currentMoveString[2]--;
7969                break;
7970              default: ; // nothing to do, but suppresses warning of pedantic compilers
7971            }
7972         }
7973         hintRequested = FALSE;
7974         lastHint[0] = NULLCHAR;
7975         bookRequested = FALSE;
7976         /* Program may be pondering now */
7977         cps->maybeThinking = TRUE;
7978         if (cps->sendTime == 2) cps->sendTime = 1;
7979         if (cps->offeredDraw) cps->offeredDraw--;
7980
7981         /* [AS] Save move info*/
7982         pvInfoList[ forwardMostMove ].score = programStats.score;
7983         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7984         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7985
7986         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7987
7988         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7989         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7990             int count = 0;
7991
7992             while( count < adjudicateLossPlies ) {
7993                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7994
7995                 if( count & 1 ) {
7996                     score = -score; /* Flip score for winning side */
7997                 }
7998
7999                 if( score > adjudicateLossThreshold ) {
8000                     break;
8001                 }
8002
8003                 count++;
8004             }
8005
8006             if( count >= adjudicateLossPlies ) {
8007                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8008
8009                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8010                     "Xboard adjudication",
8011                     GE_XBOARD );
8012
8013                 return;
8014             }
8015         }
8016
8017         if(Adjudicate(cps)) {
8018             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8019             return; // [HGM] adjudicate: for all automatic game ends
8020         }
8021
8022 #if ZIPPY
8023         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8024             first.initDone) {
8025           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8026                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8027                 SendToICS("draw ");
8028                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8029           }
8030           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8031           ics_user_moved = 1;
8032           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8033                 char buf[3*MSG_SIZ];
8034
8035                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8036                         programStats.score / 100.,
8037                         programStats.depth,
8038                         programStats.time / 100.,
8039                         (unsigned int)programStats.nodes,
8040                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8041                         programStats.movelist);
8042                 SendToICS(buf);
8043 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8044           }
8045         }
8046 #endif
8047
8048         /* [AS] Clear stats for next move */
8049         ClearProgramStats();
8050         thinkOutput[0] = NULLCHAR;
8051         hiddenThinkOutputState = 0;
8052
8053         bookHit = NULL;
8054         if (gameMode == TwoMachinesPlay) {
8055             /* [HGM] relaying draw offers moved to after reception of move */
8056             /* and interpreting offer as claim if it brings draw condition */
8057             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8058                 SendToProgram("draw\n", cps->other);
8059             }
8060             if (cps->other->sendTime) {
8061                 SendTimeRemaining(cps->other,
8062                                   cps->other->twoMachinesColor[0] == 'w');
8063             }
8064             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8065             if (firstMove && !bookHit) {
8066                 firstMove = FALSE;
8067                 if (cps->other->useColors) {
8068                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8069                 }
8070                 SendToProgram("go\n", cps->other);
8071             }
8072             cps->other->maybeThinking = TRUE;
8073         }
8074
8075         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8076
8077         if (!pausing && appData.ringBellAfterMoves) {
8078             RingBell();
8079         }
8080
8081         /*
8082          * Reenable menu items that were disabled while
8083          * machine was thinking
8084          */
8085         if (gameMode != TwoMachinesPlay)
8086             SetUserThinkingEnables();
8087
8088         // [HGM] book: after book hit opponent has received move and is now in force mode
8089         // force the book reply into it, and then fake that it outputted this move by jumping
8090         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8091         if(bookHit) {
8092                 static char bookMove[MSG_SIZ]; // a bit generous?
8093
8094                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8095                 strcat(bookMove, bookHit);
8096                 message = bookMove;
8097                 cps = cps->other;
8098                 programStats.nodes = programStats.depth = programStats.time =
8099                 programStats.score = programStats.got_only_move = 0;
8100                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8101
8102                 if(cps->lastPing != cps->lastPong) {
8103                     savedMessage = message; // args for deferred call
8104                     savedState = cps;
8105                     ScheduleDelayedEvent(DeferredBookMove, 10);
8106                     return;
8107                 }
8108                 goto FakeBookMove;
8109         }
8110
8111         return;
8112     }
8113
8114     /* Set special modes for chess engines.  Later something general
8115      *  could be added here; for now there is just one kludge feature,
8116      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8117      *  when "xboard" is given as an interactive command.
8118      */
8119     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8120         cps->useSigint = FALSE;
8121         cps->useSigterm = FALSE;
8122     }
8123     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8124       ParseFeatures(message+8, cps);
8125       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8126     }
8127
8128     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8129                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8130       int dummy, s=6; char buf[MSG_SIZ];
8131       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8132       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8133       if(startedFromSetupPosition) return;
8134       ParseFEN(boards[0], &dummy, message+s);
8135       DrawPosition(TRUE, boards[0]);
8136       startedFromSetupPosition = TRUE;
8137       return;
8138     }
8139     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8140      * want this, I was asked to put it in, and obliged.
8141      */
8142     if (!strncmp(message, "setboard ", 9)) {
8143         Board initial_position;
8144
8145         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8146
8147         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8148             DisplayError(_("Bad FEN received from engine"), 0);
8149             return ;
8150         } else {
8151            Reset(TRUE, FALSE);
8152            CopyBoard(boards[0], initial_position);
8153            initialRulePlies = FENrulePlies;
8154            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8155            else gameMode = MachinePlaysBlack;
8156            DrawPosition(FALSE, boards[currentMove]);
8157         }
8158         return;
8159     }
8160
8161     /*
8162      * Look for communication commands
8163      */
8164     if (!strncmp(message, "telluser ", 9)) {
8165         if(message[9] == '\\' && message[10] == '\\')
8166             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8167         PlayTellSound();
8168         DisplayNote(message + 9);
8169         return;
8170     }
8171     if (!strncmp(message, "tellusererror ", 14)) {
8172         cps->userError = 1;
8173         if(message[14] == '\\' && message[15] == '\\')
8174             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8175         PlayTellSound();
8176         DisplayError(message + 14, 0);
8177         return;
8178     }
8179     if (!strncmp(message, "tellopponent ", 13)) {
8180       if (appData.icsActive) {
8181         if (loggedOn) {
8182           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8183           SendToICS(buf1);
8184         }
8185       } else {
8186         DisplayNote(message + 13);
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellothers ", 11)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8194           SendToICS(buf1);
8195         }
8196       }
8197       return;
8198     }
8199     if (!strncmp(message, "tellall ", 8)) {
8200       if (appData.icsActive) {
8201         if (loggedOn) {
8202           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8203           SendToICS(buf1);
8204         }
8205       } else {
8206         DisplayNote(message + 8);
8207       }
8208       return;
8209     }
8210     if (strncmp(message, "warning", 7) == 0) {
8211         /* Undocumented feature, use tellusererror in new code */
8212         DisplayError(message, 0);
8213         return;
8214     }
8215     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8216         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8217         strcat(realname, " query");
8218         AskQuestion(realname, buf2, buf1, cps->pr);
8219         return;
8220     }
8221     /* Commands from the engine directly to ICS.  We don't allow these to be
8222      *  sent until we are logged on. Crafty kibitzes have been known to
8223      *  interfere with the login process.
8224      */
8225     if (loggedOn) {
8226         if (!strncmp(message, "tellics ", 8)) {
8227             SendToICS(message + 8);
8228             SendToICS("\n");
8229             return;
8230         }
8231         if (!strncmp(message, "tellicsnoalias ", 15)) {
8232             SendToICS(ics_prefix);
8233             SendToICS(message + 15);
8234             SendToICS("\n");
8235             return;
8236         }
8237         /* The following are for backward compatibility only */
8238         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8239             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8240             SendToICS(ics_prefix);
8241             SendToICS(message);
8242             SendToICS("\n");
8243             return;
8244         }
8245     }
8246     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8247         return;
8248     }
8249     /*
8250      * If the move is illegal, cancel it and redraw the board.
8251      * Also deal with other error cases.  Matching is rather loose
8252      * here to accommodate engines written before the spec.
8253      */
8254     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8255         strncmp(message, "Error", 5) == 0) {
8256         if (StrStr(message, "name") ||
8257             StrStr(message, "rating") || StrStr(message, "?") ||
8258             StrStr(message, "result") || StrStr(message, "board") ||
8259             StrStr(message, "bk") || StrStr(message, "computer") ||
8260             StrStr(message, "variant") || StrStr(message, "hint") ||
8261             StrStr(message, "random") || StrStr(message, "depth") ||
8262             StrStr(message, "accepted")) {
8263             return;
8264         }
8265         if (StrStr(message, "protover")) {
8266           /* Program is responding to input, so it's apparently done
8267              initializing, and this error message indicates it is
8268              protocol version 1.  So we don't need to wait any longer
8269              for it to initialize and send feature commands. */
8270           FeatureDone(cps, 1);
8271           cps->protocolVersion = 1;
8272           return;
8273         }
8274         cps->maybeThinking = FALSE;
8275
8276         if (StrStr(message, "draw")) {
8277             /* Program doesn't have "draw" command */
8278             cps->sendDrawOffers = 0;
8279             return;
8280         }
8281         if (cps->sendTime != 1 &&
8282             (StrStr(message, "time") || StrStr(message, "otim"))) {
8283           /* Program apparently doesn't have "time" or "otim" command */
8284           cps->sendTime = 0;
8285           return;
8286         }
8287         if (StrStr(message, "analyze")) {
8288             cps->analysisSupport = FALSE;
8289             cps->analyzing = FALSE;
8290 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8291             EditGameEvent(); // [HGM] try to preserve loaded game
8292             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8293             DisplayError(buf2, 0);
8294             return;
8295         }
8296         if (StrStr(message, "(no matching move)st")) {
8297           /* Special kludge for GNU Chess 4 only */
8298           cps->stKludge = TRUE;
8299           SendTimeControl(cps, movesPerSession, timeControl,
8300                           timeIncrement, appData.searchDepth,
8301                           searchTime);
8302           return;
8303         }
8304         if (StrStr(message, "(no matching move)sd")) {
8305           /* Special kludge for GNU Chess 4 only */
8306           cps->sdKludge = TRUE;
8307           SendTimeControl(cps, movesPerSession, timeControl,
8308                           timeIncrement, appData.searchDepth,
8309                           searchTime);
8310           return;
8311         }
8312         if (!StrStr(message, "llegal")) {
8313             return;
8314         }
8315         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8316             gameMode == IcsIdle) return;
8317         if (forwardMostMove <= backwardMostMove) return;
8318         if (pausing) PauseEvent();
8319       if(appData.forceIllegal) {
8320             // [HGM] illegal: machine refused move; force position after move into it
8321           SendToProgram("force\n", cps);
8322           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8323                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8324                 // when black is to move, while there might be nothing on a2 or black
8325                 // might already have the move. So send the board as if white has the move.
8326                 // But first we must change the stm of the engine, as it refused the last move
8327                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8328                 if(WhiteOnMove(forwardMostMove)) {
8329                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8330                     SendBoard(cps, forwardMostMove); // kludgeless board
8331                 } else {
8332                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8333                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8334                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8335                 }
8336           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8337             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8338                  gameMode == TwoMachinesPlay)
8339               SendToProgram("go\n", cps);
8340             return;
8341       } else
8342         if (gameMode == PlayFromGameFile) {
8343             /* Stop reading this game file */
8344             gameMode = EditGame;
8345             ModeHighlight();
8346         }
8347         /* [HGM] illegal-move claim should forfeit game when Xboard */
8348         /* only passes fully legal moves                            */
8349         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8350             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8351                                 "False illegal-move claim", GE_XBOARD );
8352             return; // do not take back move we tested as valid
8353         }
8354         currentMove = forwardMostMove-1;
8355         DisplayMove(currentMove-1); /* before DisplayMoveError */
8356         SwitchClocks(forwardMostMove-1); // [HGM] race
8357         DisplayBothClocks();
8358         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8359                 parseList[currentMove], _(cps->which));
8360         DisplayMoveError(buf1);
8361         DrawPosition(FALSE, boards[currentMove]);
8362         return;
8363     }
8364     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8365         /* Program has a broken "time" command that
8366            outputs a string not ending in newline.
8367            Don't use it. */
8368         cps->sendTime = 0;
8369     }
8370
8371     /*
8372      * If chess program startup fails, exit with an error message.
8373      * Attempts to recover here are futile.
8374      */
8375     if ((StrStr(message, "unknown host") != NULL)
8376         || (StrStr(message, "No remote directory") != NULL)
8377         || (StrStr(message, "not found") != NULL)
8378         || (StrStr(message, "No such file") != NULL)
8379         || (StrStr(message, "can't alloc") != NULL)
8380         || (StrStr(message, "Permission denied") != NULL)) {
8381
8382         cps->maybeThinking = FALSE;
8383         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8384                 _(cps->which), cps->program, cps->host, message);
8385         RemoveInputSource(cps->isr);
8386         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8387             if(cps == &first) appData.noChessProgram = TRUE;
8388             DisplayError(buf1, 0);
8389         }
8390         return;
8391     }
8392
8393     /*
8394      * Look for hint output
8395      */
8396     if (sscanf(message, "Hint: %s", buf1) == 1) {
8397         if (cps == &first && hintRequested) {
8398             hintRequested = FALSE;
8399             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8400                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8401                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8402                                     PosFlags(forwardMostMove),
8403                                     fromY, fromX, toY, toX, promoChar, buf1);
8404                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8405                 DisplayInformation(buf2);
8406             } else {
8407                 /* Hint move could not be parsed!? */
8408               snprintf(buf2, sizeof(buf2),
8409                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8410                         buf1, _(cps->which));
8411                 DisplayError(buf2, 0);
8412             }
8413         } else {
8414           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8415         }
8416         return;
8417     }
8418
8419     /*
8420      * Ignore other messages if game is not in progress
8421      */
8422     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8423         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8424
8425     /*
8426      * look for win, lose, draw, or draw offer
8427      */
8428     if (strncmp(message, "1-0", 3) == 0) {
8429         char *p, *q, *r = "";
8430         p = strchr(message, '{');
8431         if (p) {
8432             q = strchr(p, '}');
8433             if (q) {
8434                 *q = NULLCHAR;
8435                 r = p + 1;
8436             }
8437         }
8438         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8439         return;
8440     } else if (strncmp(message, "0-1", 3) == 0) {
8441         char *p, *q, *r = "";
8442         p = strchr(message, '{');
8443         if (p) {
8444             q = strchr(p, '}');
8445             if (q) {
8446                 *q = NULLCHAR;
8447                 r = p + 1;
8448             }
8449         }
8450         /* Kludge for Arasan 4.1 bug */
8451         if (strcmp(r, "Black resigns") == 0) {
8452             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8453             return;
8454         }
8455         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8456         return;
8457     } else if (strncmp(message, "1/2", 3) == 0) {
8458         char *p, *q, *r = "";
8459         p = strchr(message, '{');
8460         if (p) {
8461             q = strchr(p, '}');
8462             if (q) {
8463                 *q = NULLCHAR;
8464                 r = p + 1;
8465             }
8466         }
8467
8468         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8469         return;
8470
8471     } else if (strncmp(message, "White resign", 12) == 0) {
8472         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8473         return;
8474     } else if (strncmp(message, "Black resign", 12) == 0) {
8475         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strncmp(message, "White matches", 13) == 0 ||
8478                strncmp(message, "Black matches", 13) == 0   ) {
8479         /* [HGM] ignore GNUShogi noises */
8480         return;
8481     } else if (strncmp(message, "White", 5) == 0 &&
8482                message[5] != '(' &&
8483                StrStr(message, "Black") == NULL) {
8484         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "Black", 5) == 0 &&
8487                message[5] != '(') {
8488         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8489         return;
8490     } else if (strcmp(message, "resign") == 0 ||
8491                strcmp(message, "computer resigns") == 0) {
8492         switch (gameMode) {
8493           case MachinePlaysBlack:
8494           case IcsPlayingBlack:
8495             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8496             break;
8497           case MachinePlaysWhite:
8498           case IcsPlayingWhite:
8499             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8500             break;
8501           case TwoMachinesPlay:
8502             if (cps->twoMachinesColor[0] == 'w')
8503               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8504             else
8505               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8506             break;
8507           default:
8508             /* can't happen */
8509             break;
8510         }
8511         return;
8512     } else if (strncmp(message, "opponent mates", 14) == 0) {
8513         switch (gameMode) {
8514           case MachinePlaysBlack:
8515           case IcsPlayingBlack:
8516             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8517             break;
8518           case MachinePlaysWhite:
8519           case IcsPlayingWhite:
8520             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8521             break;
8522           case TwoMachinesPlay:
8523             if (cps->twoMachinesColor[0] == 'w')
8524               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8525             else
8526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8527             break;
8528           default:
8529             /* can't happen */
8530             break;
8531         }
8532         return;
8533     } else if (strncmp(message, "computer mates", 14) == 0) {
8534         switch (gameMode) {
8535           case MachinePlaysBlack:
8536           case IcsPlayingBlack:
8537             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8538             break;
8539           case MachinePlaysWhite:
8540           case IcsPlayingWhite:
8541             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8542             break;
8543           case TwoMachinesPlay:
8544             if (cps->twoMachinesColor[0] == 'w')
8545               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8546             else
8547               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8548             break;
8549           default:
8550             /* can't happen */
8551             break;
8552         }
8553         return;
8554     } else if (strncmp(message, "checkmate", 9) == 0) {
8555         if (WhiteOnMove(forwardMostMove)) {
8556             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8557         } else {
8558             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8559         }
8560         return;
8561     } else if (strstr(message, "Draw") != NULL ||
8562                strstr(message, "game is a draw") != NULL) {
8563         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8564         return;
8565     } else if (strstr(message, "offer") != NULL &&
8566                strstr(message, "draw") != NULL) {
8567 #if ZIPPY
8568         if (appData.zippyPlay && first.initDone) {
8569             /* Relay offer to ICS */
8570             SendToICS(ics_prefix);
8571             SendToICS("draw\n");
8572         }
8573 #endif
8574         cps->offeredDraw = 2; /* valid until this engine moves twice */
8575         if (gameMode == TwoMachinesPlay) {
8576             if (cps->other->offeredDraw) {
8577                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8578             /* [HGM] in two-machine mode we delay relaying draw offer      */
8579             /* until after we also have move, to see if it is really claim */
8580             }
8581         } else if (gameMode == MachinePlaysWhite ||
8582                    gameMode == MachinePlaysBlack) {
8583           if (userOfferedDraw) {
8584             DisplayInformation(_("Machine accepts your draw offer"));
8585             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8586           } else {
8587             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8588           }
8589         }
8590     }
8591
8592
8593     /*
8594      * Look for thinking output
8595      */
8596     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8597           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8598                                 ) {
8599         int plylev, mvleft, mvtot, curscore, time;
8600         char mvname[MOVE_LEN];
8601         u64 nodes; // [DM]
8602         char plyext;
8603         int ignore = FALSE;
8604         int prefixHint = FALSE;
8605         mvname[0] = NULLCHAR;
8606
8607         switch (gameMode) {
8608           case MachinePlaysBlack:
8609           case IcsPlayingBlack:
8610             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8611             break;
8612           case MachinePlaysWhite:
8613           case IcsPlayingWhite:
8614             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8615             break;
8616           case AnalyzeMode:
8617           case AnalyzeFile:
8618             break;
8619           case IcsObserving: /* [DM] icsEngineAnalyze */
8620             if (!appData.icsEngineAnalyze) ignore = TRUE;
8621             break;
8622           case TwoMachinesPlay:
8623             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8624                 ignore = TRUE;
8625             }
8626             break;
8627           default:
8628             ignore = TRUE;
8629             break;
8630         }
8631
8632         if (!ignore) {
8633             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8634             buf1[0] = NULLCHAR;
8635             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8636                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8637
8638                 if (plyext != ' ' && plyext != '\t') {
8639                     time *= 100;
8640                 }
8641
8642                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8643                 if( cps->scoreIsAbsolute &&
8644                     ( gameMode == MachinePlaysBlack ||
8645                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8646                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8647                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8648                      !WhiteOnMove(currentMove)
8649                     ) )
8650                 {
8651                     curscore = -curscore;
8652                 }
8653
8654                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8655
8656                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8657                         char buf[MSG_SIZ];
8658                         FILE *f;
8659                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8660                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8661                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8662                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8663                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8664                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8665                                 fclose(f);
8666                         } else DisplayError("failed writing PV", 0);
8667                 }
8668
8669                 tempStats.depth = plylev;
8670                 tempStats.nodes = nodes;
8671                 tempStats.time = time;
8672                 tempStats.score = curscore;
8673                 tempStats.got_only_move = 0;
8674
8675                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8676                         int ticklen;
8677
8678                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8679                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8680                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8681                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8682                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8683                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8684                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8685                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8686                 }
8687
8688                 /* Buffer overflow protection */
8689                 if (pv[0] != NULLCHAR) {
8690                     if (strlen(pv) >= sizeof(tempStats.movelist)
8691                         && appData.debugMode) {
8692                         fprintf(debugFP,
8693                                 "PV is too long; using the first %u bytes.\n",
8694                                 (unsigned) sizeof(tempStats.movelist) - 1);
8695                     }
8696
8697                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8698                 } else {
8699                     sprintf(tempStats.movelist, " no PV\n");
8700                 }
8701
8702                 if (tempStats.seen_stat) {
8703                     tempStats.ok_to_send = 1;
8704                 }
8705
8706                 if (strchr(tempStats.movelist, '(') != NULL) {
8707                     tempStats.line_is_book = 1;
8708                     tempStats.nr_moves = 0;
8709                     tempStats.moves_left = 0;
8710                 } else {
8711                     tempStats.line_is_book = 0;
8712                 }
8713
8714                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8715                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8716
8717                 SendProgramStatsToFrontend( cps, &tempStats );
8718
8719                 /*
8720                     [AS] Protect the thinkOutput buffer from overflow... this
8721                     is only useful if buf1 hasn't overflowed first!
8722                 */
8723                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8724                          plylev,
8725                          (gameMode == TwoMachinesPlay ?
8726                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8727                          ((double) curscore) / 100.0,
8728                          prefixHint ? lastHint : "",
8729                          prefixHint ? " " : "" );
8730
8731                 if( buf1[0] != NULLCHAR ) {
8732                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8733
8734                     if( strlen(pv) > max_len ) {
8735                         if( appData.debugMode) {
8736                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8737                         }
8738                         pv[max_len+1] = '\0';
8739                     }
8740
8741                     strcat( thinkOutput, pv);
8742                 }
8743
8744                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8745                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749
8750             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8751                 /* crafty (9.25+) says "(only move) <move>"
8752                  * if there is only 1 legal move
8753                  */
8754                 sscanf(p, "(only move) %s", buf1);
8755                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8756                 sprintf(programStats.movelist, "%s (only move)", buf1);
8757                 programStats.depth = 1;
8758                 programStats.nr_moves = 1;
8759                 programStats.moves_left = 1;
8760                 programStats.nodes = 1;
8761                 programStats.time = 1;
8762                 programStats.got_only_move = 1;
8763
8764                 /* Not really, but we also use this member to
8765                    mean "line isn't going to change" (Crafty
8766                    isn't searching, so stats won't change) */
8767                 programStats.line_is_book = 1;
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8772                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8773                     DisplayMove(currentMove - 1);
8774                 }
8775                 return;
8776             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8777                               &time, &nodes, &plylev, &mvleft,
8778                               &mvtot, mvname) >= 5) {
8779                 /* The stat01: line is from Crafty (9.29+) in response
8780                    to the "." command */
8781                 programStats.seen_stat = 1;
8782                 cps->maybeThinking = TRUE;
8783
8784                 if (programStats.got_only_move || !appData.periodicUpdates)
8785                   return;
8786
8787                 programStats.depth = plylev;
8788                 programStats.time = time;
8789                 programStats.nodes = nodes;
8790                 programStats.moves_left = mvleft;
8791                 programStats.nr_moves = mvtot;
8792                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8793                 programStats.ok_to_send = 1;
8794                 programStats.movelist[0] = '\0';
8795
8796                 SendProgramStatsToFrontend( cps, &programStats );
8797
8798                 return;
8799
8800             } else if (strncmp(message,"++",2) == 0) {
8801                 /* Crafty 9.29+ outputs this */
8802                 programStats.got_fail = 2;
8803                 return;
8804
8805             } else if (strncmp(message,"--",2) == 0) {
8806                 /* Crafty 9.29+ outputs this */
8807                 programStats.got_fail = 1;
8808                 return;
8809
8810             } else if (thinkOutput[0] != NULLCHAR &&
8811                        strncmp(message, "    ", 4) == 0) {
8812                 unsigned message_len;
8813
8814                 p = message;
8815                 while (*p && *p == ' ') p++;
8816
8817                 message_len = strlen( p );
8818
8819                 /* [AS] Avoid buffer overflow */
8820                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8821                     strcat(thinkOutput, " ");
8822                     strcat(thinkOutput, p);
8823                 }
8824
8825                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8826                     strcat(programStats.movelist, " ");
8827                     strcat(programStats.movelist, p);
8828                 }
8829
8830                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8831                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8832                     DisplayMove(currentMove - 1);
8833                 }
8834                 return;
8835             }
8836         }
8837         else {
8838             buf1[0] = NULLCHAR;
8839
8840             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8841                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8842             {
8843                 ChessProgramStats cpstats;
8844
8845                 if (plyext != ' ' && plyext != '\t') {
8846                     time *= 100;
8847                 }
8848
8849                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8850                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8851                     curscore = -curscore;
8852                 }
8853
8854                 cpstats.depth = plylev;
8855                 cpstats.nodes = nodes;
8856                 cpstats.time = time;
8857                 cpstats.score = curscore;
8858                 cpstats.got_only_move = 0;
8859                 cpstats.movelist[0] = '\0';
8860
8861                 if (buf1[0] != NULLCHAR) {
8862                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8863                 }
8864
8865                 cpstats.ok_to_send = 0;
8866                 cpstats.line_is_book = 0;
8867                 cpstats.nr_moves = 0;
8868                 cpstats.moves_left = 0;
8869
8870                 SendProgramStatsToFrontend( cps, &cpstats );
8871             }
8872         }
8873     }
8874 }
8875
8876
8877 /* Parse a game score from the character string "game", and
8878    record it as the history of the current game.  The game
8879    score is NOT assumed to start from the standard position.
8880    The display is not updated in any way.
8881    */
8882 void
8883 ParseGameHistory(game)
8884      char *game;
8885 {
8886     ChessMove moveType;
8887     int fromX, fromY, toX, toY, boardIndex;
8888     char promoChar;
8889     char *p, *q;
8890     char buf[MSG_SIZ];
8891
8892     if (appData.debugMode)
8893       fprintf(debugFP, "Parsing game history: %s\n", game);
8894
8895     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8896     gameInfo.site = StrSave(appData.icsHost);
8897     gameInfo.date = PGNDate();
8898     gameInfo.round = StrSave("-");
8899
8900     /* Parse out names of players */
8901     while (*game == ' ') game++;
8902     p = buf;
8903     while (*game != ' ') *p++ = *game++;
8904     *p = NULLCHAR;
8905     gameInfo.white = StrSave(buf);
8906     while (*game == ' ') game++;
8907     p = buf;
8908     while (*game != ' ' && *game != '\n') *p++ = *game++;
8909     *p = NULLCHAR;
8910     gameInfo.black = StrSave(buf);
8911
8912     /* Parse moves */
8913     boardIndex = blackPlaysFirst ? 1 : 0;
8914     yynewstr(game);
8915     for (;;) {
8916         yyboardindex = boardIndex;
8917         moveType = (ChessMove) Myylex();
8918         switch (moveType) {
8919           case IllegalMove:             /* maybe suicide chess, etc. */
8920   if (appData.debugMode) {
8921     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8922     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8923     setbuf(debugFP, NULL);
8924   }
8925           case WhitePromotion:
8926           case BlackPromotion:
8927           case WhiteNonPromotion:
8928           case BlackNonPromotion:
8929           case NormalMove:
8930           case WhiteCapturesEnPassant:
8931           case BlackCapturesEnPassant:
8932           case WhiteKingSideCastle:
8933           case WhiteQueenSideCastle:
8934           case BlackKingSideCastle:
8935           case BlackQueenSideCastle:
8936           case WhiteKingSideCastleWild:
8937           case WhiteQueenSideCastleWild:
8938           case BlackKingSideCastleWild:
8939           case BlackQueenSideCastleWild:
8940           /* PUSH Fabien */
8941           case WhiteHSideCastleFR:
8942           case WhiteASideCastleFR:
8943           case BlackHSideCastleFR:
8944           case BlackASideCastleFR:
8945           /* POP Fabien */
8946             fromX = currentMoveString[0] - AAA;
8947             fromY = currentMoveString[1] - ONE;
8948             toX = currentMoveString[2] - AAA;
8949             toY = currentMoveString[3] - ONE;
8950             promoChar = currentMoveString[4];
8951             break;
8952           case WhiteDrop:
8953           case BlackDrop:
8954             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8955             fromX = moveType == WhiteDrop ?
8956               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8957             (int) CharToPiece(ToLower(currentMoveString[0]));
8958             fromY = DROP_RANK;
8959             toX = currentMoveString[2] - AAA;
8960             toY = currentMoveString[3] - ONE;
8961             promoChar = NULLCHAR;
8962             break;
8963           case AmbiguousMove:
8964             /* bug? */
8965             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8966   if (appData.debugMode) {
8967     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8968     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8969     setbuf(debugFP, NULL);
8970   }
8971             DisplayError(buf, 0);
8972             return;
8973           case ImpossibleMove:
8974             /* bug? */
8975             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8976   if (appData.debugMode) {
8977     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8978     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8979     setbuf(debugFP, NULL);
8980   }
8981             DisplayError(buf, 0);
8982             return;
8983           case EndOfFile:
8984             if (boardIndex < backwardMostMove) {
8985                 /* Oops, gap.  How did that happen? */
8986                 DisplayError(_("Gap in move list"), 0);
8987                 return;
8988             }
8989             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8990             if (boardIndex > forwardMostMove) {
8991                 forwardMostMove = boardIndex;
8992             }
8993             return;
8994           case ElapsedTime:
8995             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8996                 strcat(parseList[boardIndex-1], " ");
8997                 strcat(parseList[boardIndex-1], yy_text);
8998             }
8999             continue;
9000           case Comment:
9001           case PGNTag:
9002           case NAG:
9003           default:
9004             /* ignore */
9005             continue;
9006           case WhiteWins:
9007           case BlackWins:
9008           case GameIsDrawn:
9009           case GameUnfinished:
9010             if (gameMode == IcsExamining) {
9011                 if (boardIndex < backwardMostMove) {
9012                     /* Oops, gap.  How did that happen? */
9013                     return;
9014                 }
9015                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9016                 return;
9017             }
9018             gameInfo.result = moveType;
9019             p = strchr(yy_text, '{');
9020             if (p == NULL) p = strchr(yy_text, '(');
9021             if (p == NULL) {
9022                 p = yy_text;
9023                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9024             } else {
9025                 q = strchr(p, *p == '{' ? '}' : ')');
9026                 if (q != NULL) *q = NULLCHAR;
9027                 p++;
9028             }
9029             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9030             gameInfo.resultDetails = StrSave(p);
9031             continue;
9032         }
9033         if (boardIndex >= forwardMostMove &&
9034             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9035             backwardMostMove = blackPlaysFirst ? 1 : 0;
9036             return;
9037         }
9038         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9039                                  fromY, fromX, toY, toX, promoChar,
9040                                  parseList[boardIndex]);
9041         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9042         /* currentMoveString is set as a side-effect of yylex */
9043         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9044         strcat(moveList[boardIndex], "\n");
9045         boardIndex++;
9046         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9047         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9048           case MT_NONE:
9049           case MT_STALEMATE:
9050           default:
9051             break;
9052           case MT_CHECK:
9053             if(gameInfo.variant != VariantShogi)
9054                 strcat(parseList[boardIndex - 1], "+");
9055             break;
9056           case MT_CHECKMATE:
9057           case MT_STAINMATE:
9058             strcat(parseList[boardIndex - 1], "#");
9059             break;
9060         }
9061     }
9062 }
9063
9064
9065 /* Apply a move to the given board  */
9066 void
9067 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9068      int fromX, fromY, toX, toY;
9069      int promoChar;
9070      Board board;
9071 {
9072   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9073   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9074
9075     /* [HGM] compute & store e.p. status and castling rights for new position */
9076     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9077
9078       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9079       oldEP = (signed char)board[EP_STATUS];
9080       board[EP_STATUS] = EP_NONE;
9081
9082   if (fromY == DROP_RANK) {
9083         /* must be first */
9084         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9085             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9086             return;
9087         }
9088         piece = board[toY][toX] = (ChessSquare) fromX;
9089   } else {
9090       int i;
9091
9092       if( board[toY][toX] != EmptySquare )
9093            board[EP_STATUS] = EP_CAPTURE;
9094
9095       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9096            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9097                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9098       } else
9099       if( board[fromY][fromX] == WhitePawn ) {
9100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9101                board[EP_STATUS] = EP_PAWN_MOVE;
9102            if( toY-fromY==2) {
9103                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9104                         gameInfo.variant != VariantBerolina || toX < fromX)
9105                       board[EP_STATUS] = toX | berolina;
9106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9107                         gameInfo.variant != VariantBerolina || toX > fromX)
9108                       board[EP_STATUS] = toX;
9109            }
9110       } else
9111       if( board[fromY][fromX] == BlackPawn ) {
9112            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9113                board[EP_STATUS] = EP_PAWN_MOVE;
9114            if( toY-fromY== -2) {
9115                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9116                         gameInfo.variant != VariantBerolina || toX < fromX)
9117                       board[EP_STATUS] = toX | berolina;
9118                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9119                         gameInfo.variant != VariantBerolina || toX > fromX)
9120                       board[EP_STATUS] = toX;
9121            }
9122        }
9123
9124        for(i=0; i<nrCastlingRights; i++) {
9125            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9126               board[CASTLING][i] == toX   && castlingRank[i] == toY
9127              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9128        }
9129
9130      if (fromX == toX && fromY == toY) return;
9131
9132      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9133      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9134      if(gameInfo.variant == VariantKnightmate)
9135          king += (int) WhiteUnicorn - (int) WhiteKing;
9136
9137     /* Code added by Tord: */
9138     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9139     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9140         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == WhiteRook)) {
9144         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9145       } else {
9146         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9147       }
9148     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9149                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9150       board[fromY][fromX] = EmptySquare;
9151       board[toY][toX] = EmptySquare;
9152       if((toX > fromX) != (piece == BlackRook)) {
9153         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9154       } else {
9155         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9156       }
9157     /* End of code added by Tord */
9158
9159     } else if (board[fromY][fromX] == king
9160         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9161         && toY == fromY && toX > fromX+1) {
9162         board[fromY][fromX] = EmptySquare;
9163         board[toY][toX] = king;
9164         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9165         board[fromY][BOARD_RGHT-1] = EmptySquare;
9166     } else if (board[fromY][fromX] == king
9167         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9168                && toY == fromY && toX < fromX-1) {
9169         board[fromY][fromX] = EmptySquare;
9170         board[toY][toX] = king;
9171         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9172         board[fromY][BOARD_LEFT] = EmptySquare;
9173     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9174                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9175                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9176                ) {
9177         /* white pawn promotion */
9178         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9179         if(gameInfo.variant==VariantBughouse ||
9180            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9181             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9182         board[fromY][fromX] = EmptySquare;
9183     } else if ((fromY >= BOARD_HEIGHT>>1)
9184                && (toX != fromX)
9185                && gameInfo.variant != VariantXiangqi
9186                && gameInfo.variant != VariantBerolina
9187                && (board[fromY][fromX] == WhitePawn)
9188                && (board[toY][toX] == EmptySquare)) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = WhitePawn;
9191         captured = board[toY - 1][toX];
9192         board[toY - 1][toX] = EmptySquare;
9193     } else if ((fromY == BOARD_HEIGHT-4)
9194                && (toX == fromX)
9195                && gameInfo.variant == VariantBerolina
9196                && (board[fromY][fromX] == WhitePawn)
9197                && (board[toY][toX] == EmptySquare)) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = WhitePawn;
9200         if(oldEP & EP_BEROLIN_A) {
9201                 captured = board[fromY][fromX-1];
9202                 board[fromY][fromX-1] = EmptySquare;
9203         }else{  captured = board[fromY][fromX+1];
9204                 board[fromY][fromX+1] = EmptySquare;
9205         }
9206     } else if (board[fromY][fromX] == king
9207         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9208                && toY == fromY && toX > fromX+1) {
9209         board[fromY][fromX] = EmptySquare;
9210         board[toY][toX] = king;
9211         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9212         board[fromY][BOARD_RGHT-1] = EmptySquare;
9213     } else if (board[fromY][fromX] == king
9214         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9215                && toY == fromY && toX < fromX-1) {
9216         board[fromY][fromX] = EmptySquare;
9217         board[toY][toX] = king;
9218         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9219         board[fromY][BOARD_LEFT] = EmptySquare;
9220     } else if (fromY == 7 && fromX == 3
9221                && board[fromY][fromX] == BlackKing
9222                && toY == 7 && toX == 5) {
9223         board[fromY][fromX] = EmptySquare;
9224         board[toY][toX] = BlackKing;
9225         board[fromY][7] = EmptySquare;
9226         board[toY][4] = BlackRook;
9227     } else if (fromY == 7 && fromX == 3
9228                && board[fromY][fromX] == BlackKing
9229                && toY == 7 && toX == 1) {
9230         board[fromY][fromX] = EmptySquare;
9231         board[toY][toX] = BlackKing;
9232         board[fromY][0] = EmptySquare;
9233         board[toY][2] = BlackRook;
9234     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9235                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9236                && toY < promoRank && promoChar
9237                ) {
9238         /* black pawn promotion */
9239         board[toY][toX] = CharToPiece(ToLower(promoChar));
9240         if(gameInfo.variant==VariantBughouse ||
9241            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9242             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9243         board[fromY][fromX] = EmptySquare;
9244     } else if ((fromY < BOARD_HEIGHT>>1)
9245                && (toX != fromX)
9246                && gameInfo.variant != VariantXiangqi
9247                && gameInfo.variant != VariantBerolina
9248                && (board[fromY][fromX] == BlackPawn)
9249                && (board[toY][toX] == EmptySquare)) {
9250         board[fromY][fromX] = EmptySquare;
9251         board[toY][toX] = BlackPawn;
9252         captured = board[toY + 1][toX];
9253         board[toY + 1][toX] = EmptySquare;
9254     } else if ((fromY == 3)
9255                && (toX == fromX)
9256                && gameInfo.variant == VariantBerolina
9257                && (board[fromY][fromX] == BlackPawn)
9258                && (board[toY][toX] == EmptySquare)) {
9259         board[fromY][fromX] = EmptySquare;
9260         board[toY][toX] = BlackPawn;
9261         if(oldEP & EP_BEROLIN_A) {
9262                 captured = board[fromY][fromX-1];
9263                 board[fromY][fromX-1] = EmptySquare;
9264         }else{  captured = board[fromY][fromX+1];
9265                 board[fromY][fromX+1] = EmptySquare;
9266         }
9267     } else {
9268         board[toY][toX] = board[fromY][fromX];
9269         board[fromY][fromX] = EmptySquare;
9270     }
9271   }
9272
9273     if (gameInfo.holdingsWidth != 0) {
9274
9275       /* !!A lot more code needs to be written to support holdings  */
9276       /* [HGM] OK, so I have written it. Holdings are stored in the */
9277       /* penultimate board files, so they are automaticlly stored   */
9278       /* in the game history.                                       */
9279       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9280                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9281         /* Delete from holdings, by decreasing count */
9282         /* and erasing image if necessary            */
9283         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9284         if(p < (int) BlackPawn) { /* white drop */
9285              p -= (int)WhitePawn;
9286                  p = PieceToNumber((ChessSquare)p);
9287              if(p >= gameInfo.holdingsSize) p = 0;
9288              if(--board[p][BOARD_WIDTH-2] <= 0)
9289                   board[p][BOARD_WIDTH-1] = EmptySquare;
9290              if((int)board[p][BOARD_WIDTH-2] < 0)
9291                         board[p][BOARD_WIDTH-2] = 0;
9292         } else {                  /* black drop */
9293              p -= (int)BlackPawn;
9294                  p = PieceToNumber((ChessSquare)p);
9295              if(p >= gameInfo.holdingsSize) p = 0;
9296              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9297                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9298              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9299                         board[BOARD_HEIGHT-1-p][1] = 0;
9300         }
9301       }
9302       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9303           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9304         /* [HGM] holdings: Add to holdings, if holdings exist */
9305         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9306                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9307                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9308         }
9309         p = (int) captured;
9310         if (p >= (int) BlackPawn) {
9311           p -= (int)BlackPawn;
9312           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9313                   /* in Shogi restore piece to its original  first */
9314                   captured = (ChessSquare) (DEMOTED captured);
9315                   p = DEMOTED p;
9316           }
9317           p = PieceToNumber((ChessSquare)p);
9318           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9319           board[p][BOARD_WIDTH-2]++;
9320           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9321         } else {
9322           p -= (int)WhitePawn;
9323           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9324                   captured = (ChessSquare) (DEMOTED captured);
9325                   p = DEMOTED p;
9326           }
9327           p = PieceToNumber((ChessSquare)p);
9328           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9329           board[BOARD_HEIGHT-1-p][1]++;
9330           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9331         }
9332       }
9333     } else if (gameInfo.variant == VariantAtomic) {
9334       if (captured != EmptySquare) {
9335         int y, x;
9336         for (y = toY-1; y <= toY+1; y++) {
9337           for (x = toX-1; x <= toX+1; x++) {
9338             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9339                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9340               board[y][x] = EmptySquare;
9341             }
9342           }
9343         }
9344         board[toY][toX] = EmptySquare;
9345       }
9346     }
9347     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9348         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9349     } else
9350     if(promoChar == '+') {
9351         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9352         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9353     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9354         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9355     }
9356     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9357                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9358         // [HGM] superchess: take promotion piece out of holdings
9359         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9360         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9361             if(!--board[k][BOARD_WIDTH-2])
9362                 board[k][BOARD_WIDTH-1] = EmptySquare;
9363         } else {
9364             if(!--board[BOARD_HEIGHT-1-k][1])
9365                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9366         }
9367     }
9368
9369 }
9370
9371 /* Updates forwardMostMove */
9372 void
9373 MakeMove(fromX, fromY, toX, toY, promoChar)
9374      int fromX, fromY, toX, toY;
9375      int promoChar;
9376 {
9377 //    forwardMostMove++; // [HGM] bare: moved downstream
9378
9379     (void) CoordsToAlgebraic(boards[forwardMostMove],
9380                              PosFlags(forwardMostMove),
9381                              fromY, fromX, toY, toX, promoChar,
9382                              parseList[forwardMostMove]);
9383
9384     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9385         int timeLeft; static int lastLoadFlag=0; int king, piece;
9386         piece = boards[forwardMostMove][fromY][fromX];
9387         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9388         if(gameInfo.variant == VariantKnightmate)
9389             king += (int) WhiteUnicorn - (int) WhiteKing;
9390         if(forwardMostMove == 0) {
9391             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9392                 fprintf(serverMoves, "%s;", UserName());
9393             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9394                 fprintf(serverMoves, "%s;", second.tidy);
9395             fprintf(serverMoves, "%s;", first.tidy);
9396             if(gameMode == MachinePlaysWhite)
9397                 fprintf(serverMoves, "%s;", UserName());
9398             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9399                 fprintf(serverMoves, "%s;", second.tidy);
9400         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9401         lastLoadFlag = loadFlag;
9402         // print base move
9403         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9404         // print castling suffix
9405         if( toY == fromY && piece == king ) {
9406             if(toX-fromX > 1)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9408             if(fromX-toX >1)
9409                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9410         }
9411         // e.p. suffix
9412         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9413              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9414              boards[forwardMostMove][toY][toX] == EmptySquare
9415              && fromX != toX && fromY != toY)
9416                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9417         // promotion suffix
9418         if(promoChar != NULLCHAR)
9419                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9420         if(!loadFlag) {
9421                 char buf[MOVE_LEN*2], *p; int len;
9422             fprintf(serverMoves, "/%d/%d",
9423                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9424             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9425             else                      timeLeft = blackTimeRemaining/1000;
9426             fprintf(serverMoves, "/%d", timeLeft);
9427                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9428                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9429                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9430             fprintf(serverMoves, "/%s", buf);
9431         }
9432         fflush(serverMoves);
9433     }
9434
9435     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9436         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9437       return;
9438     }
9439     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9440     if (commentList[forwardMostMove+1] != NULL) {
9441         free(commentList[forwardMostMove+1]);
9442         commentList[forwardMostMove+1] = NULL;
9443     }
9444     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9445     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9446     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9447     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9448     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9449     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9450     adjustedClock = FALSE;
9451     gameInfo.result = GameUnfinished;
9452     if (gameInfo.resultDetails != NULL) {
9453         free(gameInfo.resultDetails);
9454         gameInfo.resultDetails = NULL;
9455     }
9456     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9457                               moveList[forwardMostMove - 1]);
9458     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9459       case MT_NONE:
9460       case MT_STALEMATE:
9461       default:
9462         break;
9463       case MT_CHECK:
9464         if(gameInfo.variant != VariantShogi)
9465             strcat(parseList[forwardMostMove - 1], "+");
9466         break;
9467       case MT_CHECKMATE:
9468       case MT_STAINMATE:
9469         strcat(parseList[forwardMostMove - 1], "#");
9470         break;
9471     }
9472     if (appData.debugMode) {
9473         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9474     }
9475
9476 }
9477
9478 /* Updates currentMove if not pausing */
9479 void
9480 ShowMove(fromX, fromY, toX, toY)
9481 {
9482     int instant = (gameMode == PlayFromGameFile) ?
9483         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9484     if(appData.noGUI) return;
9485     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9486         if (!instant) {
9487             if (forwardMostMove == currentMove + 1) {
9488                 AnimateMove(boards[forwardMostMove - 1],
9489                             fromX, fromY, toX, toY);
9490             }
9491             if (appData.highlightLastMove) {
9492                 SetHighlights(fromX, fromY, toX, toY);
9493             }
9494         }
9495         currentMove = forwardMostMove;
9496     }
9497
9498     if (instant) return;
9499
9500     DisplayMove(currentMove - 1);
9501     DrawPosition(FALSE, boards[currentMove]);
9502     DisplayBothClocks();
9503     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9504 }
9505
9506 void SendEgtPath(ChessProgramState *cps)
9507 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9508         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9509
9510         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9511
9512         while(*p) {
9513             char c, *q = name+1, *r, *s;
9514
9515             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9516             while(*p && *p != ',') *q++ = *p++;
9517             *q++ = ':'; *q = 0;
9518             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9519                 strcmp(name, ",nalimov:") == 0 ) {
9520                 // take nalimov path from the menu-changeable option first, if it is defined
9521               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9522                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9523             } else
9524             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9525                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9526                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9527                 s = r = StrStr(s, ":") + 1; // beginning of path info
9528                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9529                 c = *r; *r = 0;             // temporarily null-terminate path info
9530                     *--q = 0;               // strip of trailig ':' from name
9531                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9532                 *r = c;
9533                 SendToProgram(buf,cps);     // send egtbpath command for this format
9534             }
9535             if(*p == ',') p++; // read away comma to position for next format name
9536         }
9537 }
9538
9539 void
9540 InitChessProgram(cps, setup)
9541      ChessProgramState *cps;
9542      int setup; /* [HGM] needed to setup FRC opening position */
9543 {
9544     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9545     if (appData.noChessProgram) return;
9546     hintRequested = FALSE;
9547     bookRequested = FALSE;
9548
9549     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9550     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9551     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9552     if(cps->memSize) { /* [HGM] memory */
9553       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9554         SendToProgram(buf, cps);
9555     }
9556     SendEgtPath(cps); /* [HGM] EGT */
9557     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9558       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9559         SendToProgram(buf, cps);
9560     }
9561
9562     SendToProgram(cps->initString, cps);
9563     if (gameInfo.variant != VariantNormal &&
9564         gameInfo.variant != VariantLoadable
9565         /* [HGM] also send variant if board size non-standard */
9566         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9567                                             ) {
9568       char *v = VariantName(gameInfo.variant);
9569       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9570         /* [HGM] in protocol 1 we have to assume all variants valid */
9571         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9572         DisplayFatalError(buf, 0, 1);
9573         return;
9574       }
9575
9576       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9577       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantXiangqi )
9579            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9580       if( gameInfo.variant == VariantShogi )
9581            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9582       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9584       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9585           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9586            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9587       if( gameInfo.variant == VariantCourier )
9588            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9589       if( gameInfo.variant == VariantSuper )
9590            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9591       if( gameInfo.variant == VariantGreat )
9592            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9593       if( gameInfo.variant == VariantSChess )
9594            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9595       if( gameInfo.variant == VariantGrand )
9596            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9597
9598       if(overruled) {
9599         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9600                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9601            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9602            if(StrStr(cps->variants, b) == NULL) {
9603                // specific sized variant not known, check if general sizing allowed
9604                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9605                    if(StrStr(cps->variants, "boardsize") == NULL) {
9606                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9607                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9608                        DisplayFatalError(buf, 0, 1);
9609                        return;
9610                    }
9611                    /* [HGM] here we really should compare with the maximum supported board size */
9612                }
9613            }
9614       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9615       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9616       SendToProgram(buf, cps);
9617     }
9618     currentlyInitializedVariant = gameInfo.variant;
9619
9620     /* [HGM] send opening position in FRC to first engine */
9621     if(setup) {
9622           SendToProgram("force\n", cps);
9623           SendBoard(cps, 0);
9624           /* engine is now in force mode! Set flag to wake it up after first move. */
9625           setboardSpoiledMachineBlack = 1;
9626     }
9627
9628     if (cps->sendICS) {
9629       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9630       SendToProgram(buf, cps);
9631     }
9632     cps->maybeThinking = FALSE;
9633     cps->offeredDraw = 0;
9634     if (!appData.icsActive) {
9635         SendTimeControl(cps, movesPerSession, timeControl,
9636                         timeIncrement, appData.searchDepth,
9637                         searchTime);
9638     }
9639     if (appData.showThinking
9640         // [HGM] thinking: four options require thinking output to be sent
9641         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9642                                 ) {
9643         SendToProgram("post\n", cps);
9644     }
9645     SendToProgram("hard\n", cps);
9646     if (!appData.ponderNextMove) {
9647         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9648            it without being sure what state we are in first.  "hard"
9649            is not a toggle, so that one is OK.
9650          */
9651         SendToProgram("easy\n", cps);
9652     }
9653     if (cps->usePing) {
9654       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9655       SendToProgram(buf, cps);
9656     }
9657     cps->initDone = TRUE;
9658     ClearEngineOutputPane(cps == &second);
9659 }
9660
9661
9662 void
9663 StartChessProgram(cps)
9664      ChessProgramState *cps;
9665 {
9666     char buf[MSG_SIZ];
9667     int err;
9668
9669     if (appData.noChessProgram) return;
9670     cps->initDone = FALSE;
9671
9672     if (strcmp(cps->host, "localhost") == 0) {
9673         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9674     } else if (*appData.remoteShell == NULLCHAR) {
9675         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9676     } else {
9677         if (*appData.remoteUser == NULLCHAR) {
9678           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9679                     cps->program);
9680         } else {
9681           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9682                     cps->host, appData.remoteUser, cps->program);
9683         }
9684         err = StartChildProcess(buf, "", &cps->pr);
9685     }
9686
9687     if (err != 0) {
9688       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9689         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9690         if(cps != &first) return;
9691         appData.noChessProgram = TRUE;
9692         ThawUI();
9693         SetNCPMode();
9694 //      DisplayFatalError(buf, err, 1);
9695 //      cps->pr = NoProc;
9696 //      cps->isr = NULL;
9697         return;
9698     }
9699
9700     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9701     if (cps->protocolVersion > 1) {
9702       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9703       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9704       cps->comboCnt = 0;  //                and values of combo boxes
9705       SendToProgram(buf, cps);
9706     } else {
9707       SendToProgram("xboard\n", cps);
9708     }
9709 }
9710
9711 void
9712 TwoMachinesEventIfReady P((void))
9713 {
9714   static int curMess = 0;
9715   if (first.lastPing != first.lastPong) {
9716     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9717     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9718     return;
9719   }
9720   if (second.lastPing != second.lastPong) {
9721     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9722     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9723     return;
9724   }
9725   DisplayMessage("", ""); curMess = 0;
9726   ThawUI();
9727   TwoMachinesEvent();
9728 }
9729
9730 char *
9731 MakeName(char *template)
9732 {
9733     time_t clock;
9734     struct tm *tm;
9735     static char buf[MSG_SIZ];
9736     char *p = buf;
9737     int i;
9738
9739     clock = time((time_t *)NULL);
9740     tm = localtime(&clock);
9741
9742     while(*p++ = *template++) if(p[-1] == '%') {
9743         switch(*template++) {
9744           case 0:   *p = 0; return buf;
9745           case 'Y': i = tm->tm_year+1900; break;
9746           case 'y': i = tm->tm_year-100; break;
9747           case 'M': i = tm->tm_mon+1; break;
9748           case 'd': i = tm->tm_mday; break;
9749           case 'h': i = tm->tm_hour; break;
9750           case 'm': i = tm->tm_min; break;
9751           case 's': i = tm->tm_sec; break;
9752           default:  i = 0;
9753         }
9754         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9755     }
9756     return buf;
9757 }
9758
9759 int
9760 CountPlayers(char *p)
9761 {
9762     int n = 0;
9763     while(p = strchr(p, '\n')) p++, n++; // count participants
9764     return n;
9765 }
9766
9767 FILE *
9768 WriteTourneyFile(char *results, FILE *f)
9769 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9770     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9771     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9772         // create a file with tournament description
9773         fprintf(f, "-participants {%s}\n", appData.participants);
9774         fprintf(f, "-seedBase %d\n", appData.seedBase);
9775         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9776         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9777         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9778         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9779         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9780         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9781         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9782         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9783         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9784         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9785         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9786         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9787         if(searchTime > 0)
9788                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9789         else {
9790                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9791                 fprintf(f, "-tc %s\n", appData.timeControl);
9792                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9793         }
9794         fprintf(f, "-results \"%s\"\n", results);
9795     }
9796     return f;
9797 }
9798
9799 #define MAXENGINES 1000
9800 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9801
9802 void Substitute(char *participants, int expunge)
9803 {
9804     int i, changed, changes=0, nPlayers=0;
9805     char *p, *q, *r, buf[MSG_SIZ];
9806     if(participants == NULL) return;
9807     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9808     r = p = participants; q = appData.participants;
9809     while(*p && *p == *q) {
9810         if(*p == '\n') r = p+1, nPlayers++;
9811         p++; q++;
9812     }
9813     if(*p) { // difference
9814         while(*p && *p++ != '\n');
9815         while(*q && *q++ != '\n');
9816       changed = nPlayers;
9817         changes = 1 + (strcmp(p, q) != 0);
9818     }
9819     if(changes == 1) { // a single engine mnemonic was changed
9820         q = r; while(*q) nPlayers += (*q++ == '\n');
9821         p = buf; while(*r && (*p = *r++) != '\n') p++;
9822         *p = NULLCHAR;
9823         NamesToList(firstChessProgramNames, command, mnemonic);
9824         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9825         if(mnemonic[i]) { // The substitute is valid
9826             FILE *f;
9827             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9828                 flock(fileno(f), LOCK_EX);
9829                 ParseArgsFromFile(f);
9830                 fseek(f, 0, SEEK_SET);
9831                 FREE(appData.participants); appData.participants = participants;
9832                 if(expunge) { // erase results of replaced engine
9833                     int len = strlen(appData.results), w, b, dummy;
9834                     for(i=0; i<len; i++) {
9835                         Pairing(i, nPlayers, &w, &b, &dummy);
9836                         if((w == changed || b == changed) && appData.results[i] == '*') {
9837                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9838                             fclose(f);
9839                             return;
9840                         }
9841                     }
9842                     for(i=0; i<len; i++) {
9843                         Pairing(i, nPlayers, &w, &b, &dummy);
9844                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9845                     }
9846                 }
9847                 WriteTourneyFile(appData.results, f);
9848                 fclose(f); // release lock
9849                 return;
9850             }
9851         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9852     }
9853     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9854     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9855     free(participants);
9856     return;
9857 }
9858
9859 int
9860 CreateTourney(char *name)
9861 {
9862         FILE *f;
9863         if(matchMode && strcmp(name, appData.tourneyFile)) {
9864              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9865         }
9866         if(name[0] == NULLCHAR) {
9867             if(appData.participants[0])
9868                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9869             return 0;
9870         }
9871         f = fopen(name, "r");
9872         if(f) { // file exists
9873             ASSIGN(appData.tourneyFile, name);
9874             ParseArgsFromFile(f); // parse it
9875         } else {
9876             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9877             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9878                 DisplayError(_("Not enough participants"), 0);
9879                 return 0;
9880             }
9881             ASSIGN(appData.tourneyFile, name);
9882             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9883             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9884         }
9885         fclose(f);
9886         appData.noChessProgram = FALSE;
9887         appData.clockMode = TRUE;
9888         SetGNUMode();
9889         return 1;
9890 }
9891
9892 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9893 {
9894     char buf[MSG_SIZ], *p, *q;
9895     int i=1;
9896     while(*names) {
9897         p = names; q = buf;
9898         while(*p && *p != '\n') *q++ = *p++;
9899         *q = 0;
9900         if(engineList[i]) free(engineList[i]);
9901         engineList[i] = strdup(buf);
9902         if(*p == '\n') p++;
9903         TidyProgramName(engineList[i], "localhost", buf);
9904         if(engineMnemonic[i]) free(engineMnemonic[i]);
9905         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9906             strcat(buf, " (");
9907             sscanf(q + 8, "%s", buf + strlen(buf));
9908             strcat(buf, ")");
9909         }
9910         engineMnemonic[i] = strdup(buf);
9911         names = p; i++;
9912       if(i > MAXENGINES - 2) break;
9913     }
9914     engineList[i] = engineMnemonic[i] = NULL;
9915 }
9916
9917 // following implemented as macro to avoid type limitations
9918 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9919
9920 void SwapEngines(int n)
9921 {   // swap settings for first engine and other engine (so far only some selected options)
9922     int h;
9923     char *p;
9924     if(n == 0) return;
9925     SWAP(directory, p)
9926     SWAP(chessProgram, p)
9927     SWAP(isUCI, h)
9928     SWAP(hasOwnBookUCI, h)
9929     SWAP(protocolVersion, h)
9930     SWAP(reuse, h)
9931     SWAP(scoreIsAbsolute, h)
9932     SWAP(timeOdds, h)
9933     SWAP(logo, p)
9934     SWAP(pgnName, p)
9935     SWAP(pvSAN, h)
9936     SWAP(engOptions, p)
9937 }
9938
9939 void
9940 SetPlayer(int player)
9941 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9942     int i;
9943     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9944     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9945     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9946     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9947     if(mnemonic[i]) {
9948         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9949         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9950         appData.firstHasOwnBookUCI = !appData.defNoBook;
9951         ParseArgsFromString(buf);
9952     }
9953     free(engineName);
9954 }
9955
9956 int
9957 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9958 {   // determine players from game number
9959     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9960
9961     if(appData.tourneyType == 0) {
9962         roundsPerCycle = (nPlayers - 1) | 1;
9963         pairingsPerRound = nPlayers / 2;
9964     } else if(appData.tourneyType > 0) {
9965         roundsPerCycle = nPlayers - appData.tourneyType;
9966         pairingsPerRound = appData.tourneyType;
9967     }
9968     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9969     gamesPerCycle = gamesPerRound * roundsPerCycle;
9970     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9971     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9972     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9973     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9974     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9975     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9976
9977     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9978     if(appData.roundSync) *syncInterval = gamesPerRound;
9979
9980     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9981
9982     if(appData.tourneyType == 0) {
9983         if(curPairing == (nPlayers-1)/2 ) {
9984             *whitePlayer = curRound;
9985             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9986         } else {
9987             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9988             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9989             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9990             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9991         }
9992     } else if(appData.tourneyType > 0) {
9993         *whitePlayer = curPairing;
9994         *blackPlayer = curRound + appData.tourneyType;
9995     }
9996
9997     // take care of white/black alternation per round. 
9998     // For cycles and games this is already taken care of by default, derived from matchGame!
9999     return curRound & 1;
10000 }
10001
10002 int
10003 NextTourneyGame(int nr, int *swapColors)
10004 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10005     char *p, *q;
10006     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10007     FILE *tf;
10008     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10009     tf = fopen(appData.tourneyFile, "r");
10010     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10011     ParseArgsFromFile(tf); fclose(tf);
10012     InitTimeControls(); // TC might be altered from tourney file
10013
10014     nPlayers = CountPlayers(appData.participants); // count participants
10015     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10016     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10017
10018     if(syncInterval) {
10019         p = q = appData.results;
10020         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10021         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10022             DisplayMessage(_("Waiting for other game(s)"),"");
10023             waitingForGame = TRUE;
10024             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10025             return 0;
10026         }
10027         waitingForGame = FALSE;
10028     }
10029
10030     if(appData.tourneyType < 0) {
10031         if(nr>=0 && !pairingReceived) {
10032             char buf[1<<16];
10033             if(pairing.pr == NoProc) {
10034                 if(!appData.pairingEngine[0]) {
10035                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10036                     return 0;
10037                 }
10038                 StartChessProgram(&pairing); // starts the pairing engine
10039             }
10040             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10041             SendToProgram(buf, &pairing);
10042             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10043             SendToProgram(buf, &pairing);
10044             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10045         }
10046         pairingReceived = 0;                              // ... so we continue here 
10047         *swapColors = 0;
10048         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10049         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10050         matchGame = 1; roundNr = nr / syncInterval + 1;
10051     }
10052
10053     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10054
10055     // redefine engines, engine dir, etc.
10056     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10057     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10058     SwapEngines(1);
10059     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10060     SwapEngines(1);         // and make that valid for second engine by swapping
10061     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10062     InitEngine(&second, 1);
10063     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10064     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10065     return 1;
10066 }
10067
10068 void
10069 NextMatchGame()
10070 {   // performs game initialization that does not invoke engines, and then tries to start the game
10071     int res, firstWhite, swapColors = 0;
10072     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10073     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10074     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10075     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10076     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10077     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10078     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10079     Reset(FALSE, first.pr != NoProc);
10080     res = LoadGameOrPosition(matchGame); // setup game
10081     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10082     if(!res) return; // abort when bad game/pos file
10083     TwoMachinesEvent();
10084 }
10085
10086 void UserAdjudicationEvent( int result )
10087 {
10088     ChessMove gameResult = GameIsDrawn;
10089
10090     if( result > 0 ) {
10091         gameResult = WhiteWins;
10092     }
10093     else if( result < 0 ) {
10094         gameResult = BlackWins;
10095     }
10096
10097     if( gameMode == TwoMachinesPlay ) {
10098         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10099     }
10100 }
10101
10102
10103 // [HGM] save: calculate checksum of game to make games easily identifiable
10104 int StringCheckSum(char *s)
10105 {
10106         int i = 0;
10107         if(s==NULL) return 0;
10108         while(*s) i = i*259 + *s++;
10109         return i;
10110 }
10111
10112 int GameCheckSum()
10113 {
10114         int i, sum=0;
10115         for(i=backwardMostMove; i<forwardMostMove; i++) {
10116                 sum += pvInfoList[i].depth;
10117                 sum += StringCheckSum(parseList[i]);
10118                 sum += StringCheckSum(commentList[i]);
10119                 sum *= 261;
10120         }
10121         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10122         return sum + StringCheckSum(commentList[i]);
10123 } // end of save patch
10124
10125 void
10126 GameEnds(result, resultDetails, whosays)
10127      ChessMove result;
10128      char *resultDetails;
10129      int whosays;
10130 {
10131     GameMode nextGameMode;
10132     int isIcsGame;
10133     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10134
10135     if(endingGame) return; /* [HGM] crash: forbid recursion */
10136     endingGame = 1;
10137     if(twoBoards) { // [HGM] dual: switch back to one board
10138         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10139         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10140     }
10141     if (appData.debugMode) {
10142       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10143               result, resultDetails ? resultDetails : "(null)", whosays);
10144     }
10145
10146     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10147
10148     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10149         /* If we are playing on ICS, the server decides when the
10150            game is over, but the engine can offer to draw, claim
10151            a draw, or resign.
10152          */
10153 #if ZIPPY
10154         if (appData.zippyPlay && first.initDone) {
10155             if (result == GameIsDrawn) {
10156                 /* In case draw still needs to be claimed */
10157                 SendToICS(ics_prefix);
10158                 SendToICS("draw\n");
10159             } else if (StrCaseStr(resultDetails, "resign")) {
10160                 SendToICS(ics_prefix);
10161                 SendToICS("resign\n");
10162             }
10163         }
10164 #endif
10165         endingGame = 0; /* [HGM] crash */
10166         return;
10167     }
10168
10169     /* If we're loading the game from a file, stop */
10170     if (whosays == GE_FILE) {
10171       (void) StopLoadGameTimer();
10172       gameFileFP = NULL;
10173     }
10174
10175     /* Cancel draw offers */
10176     first.offeredDraw = second.offeredDraw = 0;
10177
10178     /* If this is an ICS game, only ICS can really say it's done;
10179        if not, anyone can. */
10180     isIcsGame = (gameMode == IcsPlayingWhite ||
10181                  gameMode == IcsPlayingBlack ||
10182                  gameMode == IcsObserving    ||
10183                  gameMode == IcsExamining);
10184
10185     if (!isIcsGame || whosays == GE_ICS) {
10186         /* OK -- not an ICS game, or ICS said it was done */
10187         StopClocks();
10188         if (!isIcsGame && !appData.noChessProgram)
10189           SetUserThinkingEnables();
10190
10191         /* [HGM] if a machine claims the game end we verify this claim */
10192         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10193             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10194                 char claimer;
10195                 ChessMove trueResult = (ChessMove) -1;
10196
10197                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10198                                             first.twoMachinesColor[0] :
10199                                             second.twoMachinesColor[0] ;
10200
10201                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10202                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10203                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10204                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10205                 } else
10206                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10207                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10208                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10209                 } else
10210                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10211                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10212                 }
10213
10214                 // now verify win claims, but not in drop games, as we don't understand those yet
10215                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10216                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10217                     (result == WhiteWins && claimer == 'w' ||
10218                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10219                       if (appData.debugMode) {
10220                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10221                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10222                       }
10223                       if(result != trueResult) {
10224                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10225                               result = claimer == 'w' ? BlackWins : WhiteWins;
10226                               resultDetails = buf;
10227                       }
10228                 } else
10229                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10230                     && (forwardMostMove <= backwardMostMove ||
10231                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10232                         (claimer=='b')==(forwardMostMove&1))
10233                                                                                   ) {
10234                       /* [HGM] verify: draws that were not flagged are false claims */
10235                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10236                       result = claimer == 'w' ? BlackWins : WhiteWins;
10237                       resultDetails = buf;
10238                 }
10239                 /* (Claiming a loss is accepted no questions asked!) */
10240             }
10241             /* [HGM] bare: don't allow bare King to win */
10242             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10243                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10244                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10245                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10246                && result != GameIsDrawn)
10247             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10248                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10249                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10250                         if(p >= 0 && p <= (int)WhiteKing) k++;
10251                 }
10252                 if (appData.debugMode) {
10253                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10254                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10255                 }
10256                 if(k <= 1) {
10257                         result = GameIsDrawn;
10258                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10259                         resultDetails = buf;
10260                 }
10261             }
10262         }
10263
10264
10265         if(serverMoves != NULL && !loadFlag) { char c = '=';
10266             if(result==WhiteWins) c = '+';
10267             if(result==BlackWins) c = '-';
10268             if(resultDetails != NULL)
10269                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10270         }
10271         if (resultDetails != NULL) {
10272             gameInfo.result = result;
10273             gameInfo.resultDetails = StrSave(resultDetails);
10274
10275             /* display last move only if game was not loaded from file */
10276             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10277                 DisplayMove(currentMove - 1);
10278
10279             if (forwardMostMove != 0) {
10280                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10281                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10282                                                                 ) {
10283                     if (*appData.saveGameFile != NULLCHAR) {
10284                         SaveGameToFile(appData.saveGameFile, TRUE);
10285                     } else if (appData.autoSaveGames) {
10286                         AutoSaveGame();
10287                     }
10288                     if (*appData.savePositionFile != NULLCHAR) {
10289                         SavePositionToFile(appData.savePositionFile);
10290                     }
10291                 }
10292             }
10293
10294             /* Tell program how game ended in case it is learning */
10295             /* [HGM] Moved this to after saving the PGN, just in case */
10296             /* engine died and we got here through time loss. In that */
10297             /* case we will get a fatal error writing the pipe, which */
10298             /* would otherwise lose us the PGN.                       */
10299             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10300             /* output during GameEnds should never be fatal anymore   */
10301             if (gameMode == MachinePlaysWhite ||
10302                 gameMode == MachinePlaysBlack ||
10303                 gameMode == TwoMachinesPlay ||
10304                 gameMode == IcsPlayingWhite ||
10305                 gameMode == IcsPlayingBlack ||
10306                 gameMode == BeginningOfGame) {
10307                 char buf[MSG_SIZ];
10308                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10309                         resultDetails);
10310                 if (first.pr != NoProc) {
10311                     SendToProgram(buf, &first);
10312                 }
10313                 if (second.pr != NoProc &&
10314                     gameMode == TwoMachinesPlay) {
10315                     SendToProgram(buf, &second);
10316                 }
10317             }
10318         }
10319
10320         if (appData.icsActive) {
10321             if (appData.quietPlay &&
10322                 (gameMode == IcsPlayingWhite ||
10323                  gameMode == IcsPlayingBlack)) {
10324                 SendToICS(ics_prefix);
10325                 SendToICS("set shout 1\n");
10326             }
10327             nextGameMode = IcsIdle;
10328             ics_user_moved = FALSE;
10329             /* clean up premove.  It's ugly when the game has ended and the
10330              * premove highlights are still on the board.
10331              */
10332             if (gotPremove) {
10333               gotPremove = FALSE;
10334               ClearPremoveHighlights();
10335               DrawPosition(FALSE, boards[currentMove]);
10336             }
10337             if (whosays == GE_ICS) {
10338                 switch (result) {
10339                 case WhiteWins:
10340                     if (gameMode == IcsPlayingWhite)
10341                         PlayIcsWinSound();
10342                     else if(gameMode == IcsPlayingBlack)
10343                         PlayIcsLossSound();
10344                     break;
10345                 case BlackWins:
10346                     if (gameMode == IcsPlayingBlack)
10347                         PlayIcsWinSound();
10348                     else if(gameMode == IcsPlayingWhite)
10349                         PlayIcsLossSound();
10350                     break;
10351                 case GameIsDrawn:
10352                     PlayIcsDrawSound();
10353                     break;
10354                 default:
10355                     PlayIcsUnfinishedSound();
10356                 }
10357             }
10358         } else if (gameMode == EditGame ||
10359                    gameMode == PlayFromGameFile ||
10360                    gameMode == AnalyzeMode ||
10361                    gameMode == AnalyzeFile) {
10362             nextGameMode = gameMode;
10363         } else {
10364             nextGameMode = EndOfGame;
10365         }
10366         pausing = FALSE;
10367         ModeHighlight();
10368     } else {
10369         nextGameMode = gameMode;
10370     }
10371
10372     if (appData.noChessProgram) {
10373         gameMode = nextGameMode;
10374         ModeHighlight();
10375         endingGame = 0; /* [HGM] crash */
10376         return;
10377     }
10378
10379     if (first.reuse) {
10380         /* Put first chess program into idle state */
10381         if (first.pr != NoProc &&
10382             (gameMode == MachinePlaysWhite ||
10383              gameMode == MachinePlaysBlack ||
10384              gameMode == TwoMachinesPlay ||
10385              gameMode == IcsPlayingWhite ||
10386              gameMode == IcsPlayingBlack ||
10387              gameMode == BeginningOfGame)) {
10388             SendToProgram("force\n", &first);
10389             if (first.usePing) {
10390               char buf[MSG_SIZ];
10391               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10392               SendToProgram(buf, &first);
10393             }
10394         }
10395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10396         /* Kill off first chess program */
10397         if (first.isr != NULL)
10398           RemoveInputSource(first.isr);
10399         first.isr = NULL;
10400
10401         if (first.pr != NoProc) {
10402             ExitAnalyzeMode();
10403             DoSleep( appData.delayBeforeQuit );
10404             SendToProgram("quit\n", &first);
10405             DoSleep( appData.delayAfterQuit );
10406             DestroyChildProcess(first.pr, first.useSigterm);
10407         }
10408         first.pr = NoProc;
10409     }
10410     if (second.reuse) {
10411         /* Put second chess program into idle state */
10412         if (second.pr != NoProc &&
10413             gameMode == TwoMachinesPlay) {
10414             SendToProgram("force\n", &second);
10415             if (second.usePing) {
10416               char buf[MSG_SIZ];
10417               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10418               SendToProgram(buf, &second);
10419             }
10420         }
10421     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10422         /* Kill off second chess program */
10423         if (second.isr != NULL)
10424           RemoveInputSource(second.isr);
10425         second.isr = NULL;
10426
10427         if (second.pr != NoProc) {
10428             DoSleep( appData.delayBeforeQuit );
10429             SendToProgram("quit\n", &second);
10430             DoSleep( appData.delayAfterQuit );
10431             DestroyChildProcess(second.pr, second.useSigterm);
10432         }
10433         second.pr = NoProc;
10434     }
10435
10436     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10437         char resChar = '=';
10438         switch (result) {
10439         case WhiteWins:
10440           resChar = '+';
10441           if (first.twoMachinesColor[0] == 'w') {
10442             first.matchWins++;
10443           } else {
10444             second.matchWins++;
10445           }
10446           break;
10447         case BlackWins:
10448           resChar = '-';
10449           if (first.twoMachinesColor[0] == 'b') {
10450             first.matchWins++;
10451           } else {
10452             second.matchWins++;
10453           }
10454           break;
10455         case GameUnfinished:
10456           resChar = ' ';
10457         default:
10458           break;
10459         }
10460
10461         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10462         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10463             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10464             ReserveGame(nextGame, resChar); // sets nextGame
10465             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10466             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10467         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10468
10469         if (nextGame <= appData.matchGames && !abortMatch) {
10470             gameMode = nextGameMode;
10471             matchGame = nextGame; // this will be overruled in tourney mode!
10472             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10473             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10474             endingGame = 0; /* [HGM] crash */
10475             return;
10476         } else {
10477             gameMode = nextGameMode;
10478             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10479                      first.tidy, second.tidy,
10480                      first.matchWins, second.matchWins,
10481                      appData.matchGames - (first.matchWins + second.matchWins));
10482             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10483             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10484             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10485                 first.twoMachinesColor = "black\n";
10486                 second.twoMachinesColor = "white\n";
10487             } else {
10488                 first.twoMachinesColor = "white\n";
10489                 second.twoMachinesColor = "black\n";
10490             }
10491         }
10492     }
10493     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10494         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10495       ExitAnalyzeMode();
10496     gameMode = nextGameMode;
10497     ModeHighlight();
10498     endingGame = 0;  /* [HGM] crash */
10499     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10500         if(matchMode == TRUE) { // match through command line: exit with or without popup
10501             if(ranking) {
10502                 ToNrEvent(forwardMostMove);
10503                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10504                 else ExitEvent(0);
10505             } else DisplayFatalError(buf, 0, 0);
10506         } else { // match through menu; just stop, with or without popup
10507             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10508             ModeHighlight();
10509             if(ranking){
10510                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10511             } else DisplayNote(buf);
10512       }
10513       if(ranking) free(ranking);
10514     }
10515 }
10516
10517 /* Assumes program was just initialized (initString sent).
10518    Leaves program in force mode. */
10519 void
10520 FeedMovesToProgram(cps, upto)
10521      ChessProgramState *cps;
10522      int upto;
10523 {
10524     int i;
10525
10526     if (appData.debugMode)
10527       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10528               startedFromSetupPosition ? "position and " : "",
10529               backwardMostMove, upto, cps->which);
10530     if(currentlyInitializedVariant != gameInfo.variant) {
10531       char buf[MSG_SIZ];
10532         // [HGM] variantswitch: make engine aware of new variant
10533         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10534                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10535         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10536         SendToProgram(buf, cps);
10537         currentlyInitializedVariant = gameInfo.variant;
10538     }
10539     SendToProgram("force\n", cps);
10540     if (startedFromSetupPosition) {
10541         SendBoard(cps, backwardMostMove);
10542     if (appData.debugMode) {
10543         fprintf(debugFP, "feedMoves\n");
10544     }
10545     }
10546     for (i = backwardMostMove; i < upto; i++) {
10547         SendMoveToProgram(i, cps);
10548     }
10549 }
10550
10551
10552 int
10553 ResurrectChessProgram()
10554 {
10555      /* The chess program may have exited.
10556         If so, restart it and feed it all the moves made so far. */
10557     static int doInit = 0;
10558
10559     if (appData.noChessProgram) return 1;
10560
10561     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10562         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10563         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10564         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10565     } else {
10566         if (first.pr != NoProc) return 1;
10567         StartChessProgram(&first);
10568     }
10569     InitChessProgram(&first, FALSE);
10570     FeedMovesToProgram(&first, currentMove);
10571
10572     if (!first.sendTime) {
10573         /* can't tell gnuchess what its clock should read,
10574            so we bow to its notion. */
10575         ResetClocks();
10576         timeRemaining[0][currentMove] = whiteTimeRemaining;
10577         timeRemaining[1][currentMove] = blackTimeRemaining;
10578     }
10579
10580     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10581                 appData.icsEngineAnalyze) && first.analysisSupport) {
10582       SendToProgram("analyze\n", &first);
10583       first.analyzing = TRUE;
10584     }
10585     return 1;
10586 }
10587
10588 /*
10589  * Button procedures
10590  */
10591 void
10592 Reset(redraw, init)
10593      int redraw, init;
10594 {
10595     int i;
10596
10597     if (appData.debugMode) {
10598         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10599                 redraw, init, gameMode);
10600     }
10601     CleanupTail(); // [HGM] vari: delete any stored variations
10602     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10603     pausing = pauseExamInvalid = FALSE;
10604     startedFromSetupPosition = blackPlaysFirst = FALSE;
10605     firstMove = TRUE;
10606     whiteFlag = blackFlag = FALSE;
10607     userOfferedDraw = FALSE;
10608     hintRequested = bookRequested = FALSE;
10609     first.maybeThinking = FALSE;
10610     second.maybeThinking = FALSE;
10611     first.bookSuspend = FALSE; // [HGM] book
10612     second.bookSuspend = FALSE;
10613     thinkOutput[0] = NULLCHAR;
10614     lastHint[0] = NULLCHAR;
10615     ClearGameInfo(&gameInfo);
10616     gameInfo.variant = StringToVariant(appData.variant);
10617     ics_user_moved = ics_clock_paused = FALSE;
10618     ics_getting_history = H_FALSE;
10619     ics_gamenum = -1;
10620     white_holding[0] = black_holding[0] = NULLCHAR;
10621     ClearProgramStats();
10622     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10623
10624     ResetFrontEnd();
10625     ClearHighlights();
10626     flipView = appData.flipView;
10627     ClearPremoveHighlights();
10628     gotPremove = FALSE;
10629     alarmSounded = FALSE;
10630
10631     GameEnds(EndOfFile, NULL, GE_PLAYER);
10632     if(appData.serverMovesName != NULL) {
10633         /* [HGM] prepare to make moves file for broadcasting */
10634         clock_t t = clock();
10635         if(serverMoves != NULL) fclose(serverMoves);
10636         serverMoves = fopen(appData.serverMovesName, "r");
10637         if(serverMoves != NULL) {
10638             fclose(serverMoves);
10639             /* delay 15 sec before overwriting, so all clients can see end */
10640             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10641         }
10642         serverMoves = fopen(appData.serverMovesName, "w");
10643     }
10644
10645     ExitAnalyzeMode();
10646     gameMode = BeginningOfGame;
10647     ModeHighlight();
10648     if(appData.icsActive) gameInfo.variant = VariantNormal;
10649     currentMove = forwardMostMove = backwardMostMove = 0;
10650     InitPosition(redraw);
10651     for (i = 0; i < MAX_MOVES; i++) {
10652         if (commentList[i] != NULL) {
10653             free(commentList[i]);
10654             commentList[i] = NULL;
10655         }
10656     }
10657     ResetClocks();
10658     timeRemaining[0][0] = whiteTimeRemaining;
10659     timeRemaining[1][0] = blackTimeRemaining;
10660
10661     if (first.pr == NoProc) {
10662         StartChessProgram(&first);
10663     }
10664     if (init) {
10665             InitChessProgram(&first, startedFromSetupPosition);
10666     }
10667     DisplayTitle("");
10668     DisplayMessage("", "");
10669     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10670     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10671 }
10672
10673 void
10674 AutoPlayGameLoop()
10675 {
10676     for (;;) {
10677         if (!AutoPlayOneMove())
10678           return;
10679         if (matchMode || appData.timeDelay == 0)
10680           continue;
10681         if (appData.timeDelay < 0)
10682           return;
10683         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10684         break;
10685     }
10686 }
10687
10688
10689 int
10690 AutoPlayOneMove()
10691 {
10692     int fromX, fromY, toX, toY;
10693
10694     if (appData.debugMode) {
10695       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10696     }
10697
10698     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10699       return FALSE;
10700
10701     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10702       pvInfoList[currentMove].depth = programStats.depth;
10703       pvInfoList[currentMove].score = programStats.score;
10704       pvInfoList[currentMove].time  = 0;
10705       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10706     }
10707
10708     if (currentMove >= forwardMostMove) {
10709       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10710 //      gameMode = EndOfGame;
10711 //      ModeHighlight();
10712
10713       /* [AS] Clear current move marker at the end of a game */
10714       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10715
10716       return FALSE;
10717     }
10718
10719     toX = moveList[currentMove][2] - AAA;
10720     toY = moveList[currentMove][3] - ONE;
10721
10722     if (moveList[currentMove][1] == '@') {
10723         if (appData.highlightLastMove) {
10724             SetHighlights(-1, -1, toX, toY);
10725         }
10726     } else {
10727         fromX = moveList[currentMove][0] - AAA;
10728         fromY = moveList[currentMove][1] - ONE;
10729
10730         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10731
10732         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10733
10734         if (appData.highlightLastMove) {
10735             SetHighlights(fromX, fromY, toX, toY);
10736         }
10737     }
10738     DisplayMove(currentMove);
10739     SendMoveToProgram(currentMove++, &first);
10740     DisplayBothClocks();
10741     DrawPosition(FALSE, boards[currentMove]);
10742     // [HGM] PV info: always display, routine tests if empty
10743     DisplayComment(currentMove - 1, commentList[currentMove]);
10744     return TRUE;
10745 }
10746
10747
10748 int
10749 LoadGameOneMove(readAhead)
10750      ChessMove readAhead;
10751 {
10752     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10753     char promoChar = NULLCHAR;
10754     ChessMove moveType;
10755     char move[MSG_SIZ];
10756     char *p, *q;
10757
10758     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10759         gameMode != AnalyzeMode && gameMode != Training) {
10760         gameFileFP = NULL;
10761         return FALSE;
10762     }
10763
10764     yyboardindex = forwardMostMove;
10765     if (readAhead != EndOfFile) {
10766       moveType = readAhead;
10767     } else {
10768       if (gameFileFP == NULL)
10769           return FALSE;
10770       moveType = (ChessMove) Myylex();
10771     }
10772
10773     done = FALSE;
10774     switch (moveType) {
10775       case Comment:
10776         if (appData.debugMode)
10777           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10778         p = yy_text;
10779
10780         /* append the comment but don't display it */
10781         AppendComment(currentMove, p, FALSE);
10782         return TRUE;
10783
10784       case WhiteCapturesEnPassant:
10785       case BlackCapturesEnPassant:
10786       case WhitePromotion:
10787       case BlackPromotion:
10788       case WhiteNonPromotion:
10789       case BlackNonPromotion:
10790       case NormalMove:
10791       case WhiteKingSideCastle:
10792       case WhiteQueenSideCastle:
10793       case BlackKingSideCastle:
10794       case BlackQueenSideCastle:
10795       case WhiteKingSideCastleWild:
10796       case WhiteQueenSideCastleWild:
10797       case BlackKingSideCastleWild:
10798       case BlackQueenSideCastleWild:
10799       /* PUSH Fabien */
10800       case WhiteHSideCastleFR:
10801       case WhiteASideCastleFR:
10802       case BlackHSideCastleFR:
10803       case BlackASideCastleFR:
10804       /* POP Fabien */
10805         if (appData.debugMode)
10806           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10807         fromX = currentMoveString[0] - AAA;
10808         fromY = currentMoveString[1] - ONE;
10809         toX = currentMoveString[2] - AAA;
10810         toY = currentMoveString[3] - ONE;
10811         promoChar = currentMoveString[4];
10812         break;
10813
10814       case WhiteDrop:
10815       case BlackDrop:
10816         if (appData.debugMode)
10817           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10818         fromX = moveType == WhiteDrop ?
10819           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10820         (int) CharToPiece(ToLower(currentMoveString[0]));
10821         fromY = DROP_RANK;
10822         toX = currentMoveString[2] - AAA;
10823         toY = currentMoveString[3] - ONE;
10824         break;
10825
10826       case WhiteWins:
10827       case BlackWins:
10828       case GameIsDrawn:
10829       case GameUnfinished:
10830         if (appData.debugMode)
10831           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10832         p = strchr(yy_text, '{');
10833         if (p == NULL) p = strchr(yy_text, '(');
10834         if (p == NULL) {
10835             p = yy_text;
10836             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10837         } else {
10838             q = strchr(p, *p == '{' ? '}' : ')');
10839             if (q != NULL) *q = NULLCHAR;
10840             p++;
10841         }
10842         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10843         GameEnds(moveType, p, GE_FILE);
10844         done = TRUE;
10845         if (cmailMsgLoaded) {
10846             ClearHighlights();
10847             flipView = WhiteOnMove(currentMove);
10848             if (moveType == GameUnfinished) flipView = !flipView;
10849             if (appData.debugMode)
10850               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10851         }
10852         break;
10853
10854       case EndOfFile:
10855         if (appData.debugMode)
10856           fprintf(debugFP, "Parser hit end of file\n");
10857         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10858           case MT_NONE:
10859           case MT_CHECK:
10860             break;
10861           case MT_CHECKMATE:
10862           case MT_STAINMATE:
10863             if (WhiteOnMove(currentMove)) {
10864                 GameEnds(BlackWins, "Black mates", GE_FILE);
10865             } else {
10866                 GameEnds(WhiteWins, "White mates", GE_FILE);
10867             }
10868             break;
10869           case MT_STALEMATE:
10870             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10871             break;
10872         }
10873         done = TRUE;
10874         break;
10875
10876       case MoveNumberOne:
10877         if (lastLoadGameStart == GNUChessGame) {
10878             /* GNUChessGames have numbers, but they aren't move numbers */
10879             if (appData.debugMode)
10880               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10881                       yy_text, (int) moveType);
10882             return LoadGameOneMove(EndOfFile); /* tail recursion */
10883         }
10884         /* else fall thru */
10885
10886       case XBoardGame:
10887       case GNUChessGame:
10888       case PGNTag:
10889         /* Reached start of next game in file */
10890         if (appData.debugMode)
10891           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10892         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10893           case MT_NONE:
10894           case MT_CHECK:
10895             break;
10896           case MT_CHECKMATE:
10897           case MT_STAINMATE:
10898             if (WhiteOnMove(currentMove)) {
10899                 GameEnds(BlackWins, "Black mates", GE_FILE);
10900             } else {
10901                 GameEnds(WhiteWins, "White mates", GE_FILE);
10902             }
10903             break;
10904           case MT_STALEMATE:
10905             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10906             break;
10907         }
10908         done = TRUE;
10909         break;
10910
10911       case PositionDiagram:     /* should not happen; ignore */
10912       case ElapsedTime:         /* ignore */
10913       case NAG:                 /* ignore */
10914         if (appData.debugMode)
10915           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10916                   yy_text, (int) moveType);
10917         return LoadGameOneMove(EndOfFile); /* tail recursion */
10918
10919       case IllegalMove:
10920         if (appData.testLegality) {
10921             if (appData.debugMode)
10922               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10923             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10924                     (forwardMostMove / 2) + 1,
10925                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10926             DisplayError(move, 0);
10927             done = TRUE;
10928         } else {
10929             if (appData.debugMode)
10930               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10931                       yy_text, currentMoveString);
10932             fromX = currentMoveString[0] - AAA;
10933             fromY = currentMoveString[1] - ONE;
10934             toX = currentMoveString[2] - AAA;
10935             toY = currentMoveString[3] - ONE;
10936             promoChar = currentMoveString[4];
10937         }
10938         break;
10939
10940       case AmbiguousMove:
10941         if (appData.debugMode)
10942           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10943         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10944                 (forwardMostMove / 2) + 1,
10945                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10946         DisplayError(move, 0);
10947         done = TRUE;
10948         break;
10949
10950       default:
10951       case ImpossibleMove:
10952         if (appData.debugMode)
10953           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10954         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10955                 (forwardMostMove / 2) + 1,
10956                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10957         DisplayError(move, 0);
10958         done = TRUE;
10959         break;
10960     }
10961
10962     if (done) {
10963         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10964             DrawPosition(FALSE, boards[currentMove]);
10965             DisplayBothClocks();
10966             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10967               DisplayComment(currentMove - 1, commentList[currentMove]);
10968         }
10969         (void) StopLoadGameTimer();
10970         gameFileFP = NULL;
10971         cmailOldMove = forwardMostMove;
10972         return FALSE;
10973     } else {
10974         /* currentMoveString is set as a side-effect of yylex */
10975
10976         thinkOutput[0] = NULLCHAR;
10977         MakeMove(fromX, fromY, toX, toY, promoChar);
10978         currentMove = forwardMostMove;
10979         return TRUE;
10980     }
10981 }
10982
10983 /* Load the nth game from the given file */
10984 int
10985 LoadGameFromFile(filename, n, title, useList)
10986      char *filename;
10987      int n;
10988      char *title;
10989      /*Boolean*/ int useList;
10990 {
10991     FILE *f;
10992     char buf[MSG_SIZ];
10993
10994     if (strcmp(filename, "-") == 0) {
10995         f = stdin;
10996         title = "stdin";
10997     } else {
10998         f = fopen(filename, "rb");
10999         if (f == NULL) {
11000           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11001             DisplayError(buf, errno);
11002             return FALSE;
11003         }
11004     }
11005     if (fseek(f, 0, 0) == -1) {
11006         /* f is not seekable; probably a pipe */
11007         useList = FALSE;
11008     }
11009     if (useList && n == 0) {
11010         int error = GameListBuild(f);
11011         if (error) {
11012             DisplayError(_("Cannot build game list"), error);
11013         } else if (!ListEmpty(&gameList) &&
11014                    ((ListGame *) gameList.tailPred)->number > 1) {
11015             GameListPopUp(f, title);
11016             return TRUE;
11017         }
11018         GameListDestroy();
11019         n = 1;
11020     }
11021     if (n == 0) n = 1;
11022     return LoadGame(f, n, title, FALSE);
11023 }
11024
11025
11026 void
11027 MakeRegisteredMove()
11028 {
11029     int fromX, fromY, toX, toY;
11030     char promoChar;
11031     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11032         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11033           case CMAIL_MOVE:
11034           case CMAIL_DRAW:
11035             if (appData.debugMode)
11036               fprintf(debugFP, "Restoring %s for game %d\n",
11037                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11038
11039             thinkOutput[0] = NULLCHAR;
11040             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11041             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11042             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11043             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11044             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11045             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11046             MakeMove(fromX, fromY, toX, toY, promoChar);
11047             ShowMove(fromX, fromY, toX, toY);
11048
11049             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11050               case MT_NONE:
11051               case MT_CHECK:
11052                 break;
11053
11054               case MT_CHECKMATE:
11055               case MT_STAINMATE:
11056                 if (WhiteOnMove(currentMove)) {
11057                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11058                 } else {
11059                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11060                 }
11061                 break;
11062
11063               case MT_STALEMATE:
11064                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11065                 break;
11066             }
11067
11068             break;
11069
11070           case CMAIL_RESIGN:
11071             if (WhiteOnMove(currentMove)) {
11072                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11073             } else {
11074                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11075             }
11076             break;
11077
11078           case CMAIL_ACCEPT:
11079             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11080             break;
11081
11082           default:
11083             break;
11084         }
11085     }
11086
11087     return;
11088 }
11089
11090 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11091 int
11092 CmailLoadGame(f, gameNumber, title, useList)
11093      FILE *f;
11094      int gameNumber;
11095      char *title;
11096      int useList;
11097 {
11098     int retVal;
11099
11100     if (gameNumber > nCmailGames) {
11101         DisplayError(_("No more games in this message"), 0);
11102         return FALSE;
11103     }
11104     if (f == lastLoadGameFP) {
11105         int offset = gameNumber - lastLoadGameNumber;
11106         if (offset == 0) {
11107             cmailMsg[0] = NULLCHAR;
11108             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11109                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11110                 nCmailMovesRegistered--;
11111             }
11112             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11113             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11114                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11115             }
11116         } else {
11117             if (! RegisterMove()) return FALSE;
11118         }
11119     }
11120
11121     retVal = LoadGame(f, gameNumber, title, useList);
11122
11123     /* Make move registered during previous look at this game, if any */
11124     MakeRegisteredMove();
11125
11126     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11127         commentList[currentMove]
11128           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11129         DisplayComment(currentMove - 1, commentList[currentMove]);
11130     }
11131
11132     return retVal;
11133 }
11134
11135 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11136 int
11137 ReloadGame(offset)
11138      int offset;
11139 {
11140     int gameNumber = lastLoadGameNumber + offset;
11141     if (lastLoadGameFP == NULL) {
11142         DisplayError(_("No game has been loaded yet"), 0);
11143         return FALSE;
11144     }
11145     if (gameNumber <= 0) {
11146         DisplayError(_("Can't back up any further"), 0);
11147         return FALSE;
11148     }
11149     if (cmailMsgLoaded) {
11150         return CmailLoadGame(lastLoadGameFP, gameNumber,
11151                              lastLoadGameTitle, lastLoadGameUseList);
11152     } else {
11153         return LoadGame(lastLoadGameFP, gameNumber,
11154                         lastLoadGameTitle, lastLoadGameUseList);
11155     }
11156 }
11157
11158 int keys[EmptySquare+1];
11159
11160 int
11161 PositionMatches(Board b1, Board b2)
11162 {
11163     int r, f, sum=0;
11164     switch(appData.searchMode) {
11165         case 1: return CompareWithRights(b1, b2);
11166         case 2:
11167             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11168                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11169             }
11170             return TRUE;
11171         case 3:
11172             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11173               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11174                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11175             }
11176             return sum==0;
11177         case 4:
11178             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11179                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11180             }
11181             return sum==0;
11182     }
11183     return TRUE;
11184 }
11185
11186 #define Q_PROMO  4
11187 #define Q_EP     3
11188 #define Q_BCASTL 2
11189 #define Q_WCASTL 1
11190
11191 int pieceList[256], quickBoard[256];
11192 ChessSquare pieceType[256] = { EmptySquare };
11193 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11194 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11195 int soughtTotal, turn;
11196 Boolean epOK, flipSearch;
11197
11198 typedef struct {
11199     unsigned char piece, to;
11200 } Move;
11201
11202 #define DSIZE (250000)
11203
11204 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11205 Move *moveDatabase = initialSpace;
11206 unsigned int movePtr, dataSize = DSIZE;
11207
11208 int MakePieceList(Board board, int *counts)
11209 {
11210     int r, f, n=Q_PROMO, total=0;
11211     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11212     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11213         int sq = f + (r<<4);
11214         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11215             quickBoard[sq] = ++n;
11216             pieceList[n] = sq;
11217             pieceType[n] = board[r][f];
11218             counts[board[r][f]]++;
11219             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11220             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11221             total++;
11222         }
11223     }
11224     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11225     return total;
11226 }
11227
11228 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11229 {
11230     int sq = fromX + (fromY<<4);
11231     int piece = quickBoard[sq];
11232     quickBoard[sq] = 0;
11233     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11234     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11235         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11236         moveDatabase[movePtr++].piece = Q_WCASTL;
11237         quickBoard[sq] = piece;
11238         piece = quickBoard[from]; quickBoard[from] = 0;
11239         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11240     } else
11241     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11242         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11243         moveDatabase[movePtr++].piece = Q_BCASTL;
11244         quickBoard[sq] = piece;
11245         piece = quickBoard[from]; quickBoard[from] = 0;
11246         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11247     } else
11248     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11249         quickBoard[(fromY<<4)+toX] = 0;
11250         moveDatabase[movePtr].piece = Q_EP;
11251         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11252         moveDatabase[movePtr].to = sq;
11253     } else
11254     if(promoPiece != pieceType[piece]) {
11255         moveDatabase[movePtr++].piece = Q_PROMO;
11256         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11257     }
11258     moveDatabase[movePtr].piece = piece;
11259     quickBoard[sq] = piece;
11260     movePtr++;
11261 }
11262
11263 int PackGame(Board board)
11264 {
11265     Move *newSpace = NULL;
11266     moveDatabase[movePtr].piece = 0; // terminate previous game
11267     if(movePtr > dataSize) {
11268         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11269         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11270         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11271         if(newSpace) {
11272             int i;
11273             Move *p = moveDatabase, *q = newSpace;
11274             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11275             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11276             moveDatabase = newSpace;
11277         } else { // calloc failed, we must be out of memory. Too bad...
11278             dataSize = 0; // prevent calloc events for all subsequent games
11279             return 0;     // and signal this one isn't cached
11280         }
11281     }
11282     movePtr++;
11283     MakePieceList(board, counts);
11284     return movePtr;
11285 }
11286
11287 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11288 {   // compare according to search mode
11289     int r, f;
11290     switch(appData.searchMode)
11291     {
11292       case 1: // exact position match
11293         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11294         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11296         }
11297         break;
11298       case 2: // can have extra material on empty squares
11299         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11300             if(board[r][f] == EmptySquare) continue;
11301             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11302         }
11303         break;
11304       case 3: // material with exact Pawn structure
11305         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11306             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11307             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11308         } // fall through to material comparison
11309       case 4: // exact material
11310         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11311         break;
11312       case 6: // material range with given imbalance
11313         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11314         // fall through to range comparison
11315       case 5: // material range
11316         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11317     }
11318     return TRUE;
11319 }
11320
11321 int QuickScan(Board board, Move *move)
11322 {   // reconstruct game,and compare all positions in it
11323     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11324     do {
11325         int piece = move->piece;
11326         int to = move->to, from = pieceList[piece];
11327         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11328           if(!piece) return -1;
11329           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11330             piece = (++move)->piece;
11331             from = pieceList[piece];
11332             counts[pieceType[piece]]--;
11333             pieceType[piece] = (ChessSquare) move->to;
11334             counts[move->to]++;
11335           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11336             counts[pieceType[quickBoard[to]]]--;
11337             quickBoard[to] = 0; total--;
11338             move++;
11339             continue;
11340           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11341             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11342             from  = pieceList[piece]; // so this must be King
11343             quickBoard[from] = 0;
11344             quickBoard[to] = piece;
11345             pieceList[piece] = to;
11346             move++;
11347             continue;
11348           }
11349         }
11350         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11351         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11352         quickBoard[from] = 0;
11353         quickBoard[to] = piece;
11354         pieceList[piece] = to;
11355         cnt++; turn ^= 3;
11356         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11357            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11358            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11359                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11360           ) {
11361             static int lastCounts[EmptySquare+1];
11362             int i;
11363             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11364             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11365         } else stretch = 0;
11366         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11367         move++;
11368     } while(1);
11369 }
11370
11371 void InitSearch()
11372 {
11373     int r, f;
11374     flipSearch = FALSE;
11375     CopyBoard(soughtBoard, boards[currentMove]);
11376     soughtTotal = MakePieceList(soughtBoard, maxSought);
11377     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11378     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11379     CopyBoard(reverseBoard, boards[currentMove]);
11380     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11381         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11382         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11383         reverseBoard[r][f] = piece;
11384     }
11385     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11386     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11387     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11388                  || (boards[currentMove][CASTLING][2] == NoRights || 
11389                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11390                  && (boards[currentMove][CASTLING][5] == NoRights || 
11391                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11392       ) {
11393         flipSearch = TRUE;
11394         CopyBoard(flipBoard, soughtBoard);
11395         CopyBoard(rotateBoard, reverseBoard);
11396         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11397             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11398             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11399         }
11400     }
11401     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11402     if(appData.searchMode >= 5) {
11403         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11404         MakePieceList(soughtBoard, minSought);
11405         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11406     }
11407     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11408         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11409 }
11410
11411 GameInfo dummyInfo;
11412
11413 int GameContainsPosition(FILE *f, ListGame *lg)
11414 {
11415     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11416     int fromX, fromY, toX, toY;
11417     char promoChar;
11418     static int initDone=FALSE;
11419
11420     // weed out games based on numerical tag comparison
11421     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11422     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11423     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11424     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11425     if(!initDone) {
11426         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11427         initDone = TRUE;
11428     }
11429     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11430     else CopyBoard(boards[scratch], initialPosition); // default start position
11431     if(lg->moves) {
11432         turn = btm + 1;
11433         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11434         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11435     }
11436     if(btm) plyNr++;
11437     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11438     fseek(f, lg->offset, 0);
11439     yynewfile(f);
11440     while(1) {
11441         yyboardindex = scratch;
11442         quickFlag = plyNr+1;
11443         next = Myylex();
11444         quickFlag = 0;
11445         switch(next) {
11446             case PGNTag:
11447                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11448             default:
11449                 continue;
11450
11451             case XBoardGame:
11452             case GNUChessGame:
11453                 if(plyNr) return -1; // after we have seen moves, this is for new game
11454               continue;
11455
11456             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11457             case ImpossibleMove:
11458             case WhiteWins: // game ends here with these four
11459             case BlackWins:
11460             case GameIsDrawn:
11461             case GameUnfinished:
11462                 return -1;
11463
11464             case IllegalMove:
11465                 if(appData.testLegality) return -1;
11466             case WhiteCapturesEnPassant:
11467             case BlackCapturesEnPassant:
11468             case WhitePromotion:
11469             case BlackPromotion:
11470             case WhiteNonPromotion:
11471             case BlackNonPromotion:
11472             case NormalMove:
11473             case WhiteKingSideCastle:
11474             case WhiteQueenSideCastle:
11475             case BlackKingSideCastle:
11476             case BlackQueenSideCastle:
11477             case WhiteKingSideCastleWild:
11478             case WhiteQueenSideCastleWild:
11479             case BlackKingSideCastleWild:
11480             case BlackQueenSideCastleWild:
11481             case WhiteHSideCastleFR:
11482             case WhiteASideCastleFR:
11483             case BlackHSideCastleFR:
11484             case BlackASideCastleFR:
11485                 fromX = currentMoveString[0] - AAA;
11486                 fromY = currentMoveString[1] - ONE;
11487                 toX = currentMoveString[2] - AAA;
11488                 toY = currentMoveString[3] - ONE;
11489                 promoChar = currentMoveString[4];
11490                 break;
11491             case WhiteDrop:
11492             case BlackDrop:
11493                 fromX = next == WhiteDrop ?
11494                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11495                   (int) CharToPiece(ToLower(currentMoveString[0]));
11496                 fromY = DROP_RANK;
11497                 toX = currentMoveString[2] - AAA;
11498                 toY = currentMoveString[3] - ONE;
11499                 promoChar = 0;
11500                 break;
11501         }
11502         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11503         plyNr++;
11504         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11505         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11506         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11507         if(appData.findMirror) {
11508             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11509             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11510         }
11511     }
11512 }
11513
11514 /* Load the nth game from open file f */
11515 int
11516 LoadGame(f, gameNumber, title, useList)
11517      FILE *f;
11518      int gameNumber;
11519      char *title;
11520      int useList;
11521 {
11522     ChessMove cm;
11523     char buf[MSG_SIZ];
11524     int gn = gameNumber;
11525     ListGame *lg = NULL;
11526     int numPGNTags = 0;
11527     int err, pos = -1;
11528     GameMode oldGameMode;
11529     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11530
11531     if (appData.debugMode)
11532         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11533
11534     if (gameMode == Training )
11535         SetTrainingModeOff();
11536
11537     oldGameMode = gameMode;
11538     if (gameMode != BeginningOfGame) {
11539       Reset(FALSE, TRUE);
11540     }
11541
11542     gameFileFP = f;
11543     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11544         fclose(lastLoadGameFP);
11545     }
11546
11547     if (useList) {
11548         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11549
11550         if (lg) {
11551             fseek(f, lg->offset, 0);
11552             GameListHighlight(gameNumber);
11553             pos = lg->position;
11554             gn = 1;
11555         }
11556         else {
11557             DisplayError(_("Game number out of range"), 0);
11558             return FALSE;
11559         }
11560     } else {
11561         GameListDestroy();
11562         if (fseek(f, 0, 0) == -1) {
11563             if (f == lastLoadGameFP ?
11564                 gameNumber == lastLoadGameNumber + 1 :
11565                 gameNumber == 1) {
11566                 gn = 1;
11567             } else {
11568                 DisplayError(_("Can't seek on game file"), 0);
11569                 return FALSE;
11570             }
11571         }
11572     }
11573     lastLoadGameFP = f;
11574     lastLoadGameNumber = gameNumber;
11575     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11576     lastLoadGameUseList = useList;
11577
11578     yynewfile(f);
11579
11580     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11581       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11582                 lg->gameInfo.black);
11583             DisplayTitle(buf);
11584     } else if (*title != NULLCHAR) {
11585         if (gameNumber > 1) {
11586           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11587             DisplayTitle(buf);
11588         } else {
11589             DisplayTitle(title);
11590         }
11591     }
11592
11593     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11594         gameMode = PlayFromGameFile;
11595         ModeHighlight();
11596     }
11597
11598     currentMove = forwardMostMove = backwardMostMove = 0;
11599     CopyBoard(boards[0], initialPosition);
11600     StopClocks();
11601
11602     /*
11603      * Skip the first gn-1 games in the file.
11604      * Also skip over anything that precedes an identifiable
11605      * start of game marker, to avoid being confused by
11606      * garbage at the start of the file.  Currently
11607      * recognized start of game markers are the move number "1",
11608      * the pattern "gnuchess .* game", the pattern
11609      * "^[#;%] [^ ]* game file", and a PGN tag block.
11610      * A game that starts with one of the latter two patterns
11611      * will also have a move number 1, possibly
11612      * following a position diagram.
11613      * 5-4-02: Let's try being more lenient and allowing a game to
11614      * start with an unnumbered move.  Does that break anything?
11615      */
11616     cm = lastLoadGameStart = EndOfFile;
11617     while (gn > 0) {
11618         yyboardindex = forwardMostMove;
11619         cm = (ChessMove) Myylex();
11620         switch (cm) {
11621           case EndOfFile:
11622             if (cmailMsgLoaded) {
11623                 nCmailGames = CMAIL_MAX_GAMES - gn;
11624             } else {
11625                 Reset(TRUE, TRUE);
11626                 DisplayError(_("Game not found in file"), 0);
11627             }
11628             return FALSE;
11629
11630           case GNUChessGame:
11631           case XBoardGame:
11632             gn--;
11633             lastLoadGameStart = cm;
11634             break;
11635
11636           case MoveNumberOne:
11637             switch (lastLoadGameStart) {
11638               case GNUChessGame:
11639               case XBoardGame:
11640               case PGNTag:
11641                 break;
11642               case MoveNumberOne:
11643               case EndOfFile:
11644                 gn--;           /* count this game */
11645                 lastLoadGameStart = cm;
11646                 break;
11647               default:
11648                 /* impossible */
11649                 break;
11650             }
11651             break;
11652
11653           case PGNTag:
11654             switch (lastLoadGameStart) {
11655               case GNUChessGame:
11656               case PGNTag:
11657               case MoveNumberOne:
11658               case EndOfFile:
11659                 gn--;           /* count this game */
11660                 lastLoadGameStart = cm;
11661                 break;
11662               case XBoardGame:
11663                 lastLoadGameStart = cm; /* game counted already */
11664                 break;
11665               default:
11666                 /* impossible */
11667                 break;
11668             }
11669             if (gn > 0) {
11670                 do {
11671                     yyboardindex = forwardMostMove;
11672                     cm = (ChessMove) Myylex();
11673                 } while (cm == PGNTag || cm == Comment);
11674             }
11675             break;
11676
11677           case WhiteWins:
11678           case BlackWins:
11679           case GameIsDrawn:
11680             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11681                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11682                     != CMAIL_OLD_RESULT) {
11683                     nCmailResults ++ ;
11684                     cmailResult[  CMAIL_MAX_GAMES
11685                                 - gn - 1] = CMAIL_OLD_RESULT;
11686                 }
11687             }
11688             break;
11689
11690           case NormalMove:
11691             /* Only a NormalMove can be at the start of a game
11692              * without a position diagram. */
11693             if (lastLoadGameStart == EndOfFile ) {
11694               gn--;
11695               lastLoadGameStart = MoveNumberOne;
11696             }
11697             break;
11698
11699           default:
11700             break;
11701         }
11702     }
11703
11704     if (appData.debugMode)
11705       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11706
11707     if (cm == XBoardGame) {
11708         /* Skip any header junk before position diagram and/or move 1 */
11709         for (;;) {
11710             yyboardindex = forwardMostMove;
11711             cm = (ChessMove) Myylex();
11712
11713             if (cm == EndOfFile ||
11714                 cm == GNUChessGame || cm == XBoardGame) {
11715                 /* Empty game; pretend end-of-file and handle later */
11716                 cm = EndOfFile;
11717                 break;
11718             }
11719
11720             if (cm == MoveNumberOne || cm == PositionDiagram ||
11721                 cm == PGNTag || cm == Comment)
11722               break;
11723         }
11724     } else if (cm == GNUChessGame) {
11725         if (gameInfo.event != NULL) {
11726             free(gameInfo.event);
11727         }
11728         gameInfo.event = StrSave(yy_text);
11729     }
11730
11731     startedFromSetupPosition = FALSE;
11732     while (cm == PGNTag) {
11733         if (appData.debugMode)
11734           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11735         err = ParsePGNTag(yy_text, &gameInfo);
11736         if (!err) numPGNTags++;
11737
11738         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11739         if(gameInfo.variant != oldVariant) {
11740             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11741             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11742             InitPosition(TRUE);
11743             oldVariant = gameInfo.variant;
11744             if (appData.debugMode)
11745               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11746         }
11747
11748
11749         if (gameInfo.fen != NULL) {
11750           Board initial_position;
11751           startedFromSetupPosition = TRUE;
11752           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11753             Reset(TRUE, TRUE);
11754             DisplayError(_("Bad FEN position in file"), 0);
11755             return FALSE;
11756           }
11757           CopyBoard(boards[0], initial_position);
11758           if (blackPlaysFirst) {
11759             currentMove = forwardMostMove = backwardMostMove = 1;
11760             CopyBoard(boards[1], initial_position);
11761             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11762             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11763             timeRemaining[0][1] = whiteTimeRemaining;
11764             timeRemaining[1][1] = blackTimeRemaining;
11765             if (commentList[0] != NULL) {
11766               commentList[1] = commentList[0];
11767               commentList[0] = NULL;
11768             }
11769           } else {
11770             currentMove = forwardMostMove = backwardMostMove = 0;
11771           }
11772           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11773           {   int i;
11774               initialRulePlies = FENrulePlies;
11775               for( i=0; i< nrCastlingRights; i++ )
11776                   initialRights[i] = initial_position[CASTLING][i];
11777           }
11778           yyboardindex = forwardMostMove;
11779           free(gameInfo.fen);
11780           gameInfo.fen = NULL;
11781         }
11782
11783         yyboardindex = forwardMostMove;
11784         cm = (ChessMove) Myylex();
11785
11786         /* Handle comments interspersed among the tags */
11787         while (cm == Comment) {
11788             char *p;
11789             if (appData.debugMode)
11790               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11791             p = yy_text;
11792             AppendComment(currentMove, p, FALSE);
11793             yyboardindex = forwardMostMove;
11794             cm = (ChessMove) Myylex();
11795         }
11796     }
11797
11798     /* don't rely on existence of Event tag since if game was
11799      * pasted from clipboard the Event tag may not exist
11800      */
11801     if (numPGNTags > 0){
11802         char *tags;
11803         if (gameInfo.variant == VariantNormal) {
11804           VariantClass v = StringToVariant(gameInfo.event);
11805           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11806           if(v < VariantShogi) gameInfo.variant = v;
11807         }
11808         if (!matchMode) {
11809           if( appData.autoDisplayTags ) {
11810             tags = PGNTags(&gameInfo);
11811             TagsPopUp(tags, CmailMsg());
11812             free(tags);
11813           }
11814         }
11815     } else {
11816         /* Make something up, but don't display it now */
11817         SetGameInfo();
11818         TagsPopDown();
11819     }
11820
11821     if (cm == PositionDiagram) {
11822         int i, j;
11823         char *p;
11824         Board initial_position;
11825
11826         if (appData.debugMode)
11827           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11828
11829         if (!startedFromSetupPosition) {
11830             p = yy_text;
11831             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11832               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11833                 switch (*p) {
11834                   case '{':
11835                   case '[':
11836                   case '-':
11837                   case ' ':
11838                   case '\t':
11839                   case '\n':
11840                   case '\r':
11841                     break;
11842                   default:
11843                     initial_position[i][j++] = CharToPiece(*p);
11844                     break;
11845                 }
11846             while (*p == ' ' || *p == '\t' ||
11847                    *p == '\n' || *p == '\r') p++;
11848
11849             if (strncmp(p, "black", strlen("black"))==0)
11850               blackPlaysFirst = TRUE;
11851             else
11852               blackPlaysFirst = FALSE;
11853             startedFromSetupPosition = TRUE;
11854
11855             CopyBoard(boards[0], initial_position);
11856             if (blackPlaysFirst) {
11857                 currentMove = forwardMostMove = backwardMostMove = 1;
11858                 CopyBoard(boards[1], initial_position);
11859                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11860                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11861                 timeRemaining[0][1] = whiteTimeRemaining;
11862                 timeRemaining[1][1] = blackTimeRemaining;
11863                 if (commentList[0] != NULL) {
11864                     commentList[1] = commentList[0];
11865                     commentList[0] = NULL;
11866                 }
11867             } else {
11868                 currentMove = forwardMostMove = backwardMostMove = 0;
11869             }
11870         }
11871         yyboardindex = forwardMostMove;
11872         cm = (ChessMove) Myylex();
11873     }
11874
11875     if (first.pr == NoProc) {
11876         StartChessProgram(&first);
11877     }
11878     InitChessProgram(&first, FALSE);
11879     SendToProgram("force\n", &first);
11880     if (startedFromSetupPosition) {
11881         SendBoard(&first, forwardMostMove);
11882     if (appData.debugMode) {
11883         fprintf(debugFP, "Load Game\n");
11884     }
11885         DisplayBothClocks();
11886     }
11887
11888     /* [HGM] server: flag to write setup moves in broadcast file as one */
11889     loadFlag = appData.suppressLoadMoves;
11890
11891     while (cm == Comment) {
11892         char *p;
11893         if (appData.debugMode)
11894           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11895         p = yy_text;
11896         AppendComment(currentMove, p, FALSE);
11897         yyboardindex = forwardMostMove;
11898         cm = (ChessMove) Myylex();
11899     }
11900
11901     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11902         cm == WhiteWins || cm == BlackWins ||
11903         cm == GameIsDrawn || cm == GameUnfinished) {
11904         DisplayMessage("", _("No moves in game"));
11905         if (cmailMsgLoaded) {
11906             if (appData.debugMode)
11907               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11908             ClearHighlights();
11909             flipView = FALSE;
11910         }
11911         DrawPosition(FALSE, boards[currentMove]);
11912         DisplayBothClocks();
11913         gameMode = EditGame;
11914         ModeHighlight();
11915         gameFileFP = NULL;
11916         cmailOldMove = 0;
11917         return TRUE;
11918     }
11919
11920     // [HGM] PV info: routine tests if comment empty
11921     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11922         DisplayComment(currentMove - 1, commentList[currentMove]);
11923     }
11924     if (!matchMode && appData.timeDelay != 0)
11925       DrawPosition(FALSE, boards[currentMove]);
11926
11927     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11928       programStats.ok_to_send = 1;
11929     }
11930
11931     /* if the first token after the PGN tags is a move
11932      * and not move number 1, retrieve it from the parser
11933      */
11934     if (cm != MoveNumberOne)
11935         LoadGameOneMove(cm);
11936
11937     /* load the remaining moves from the file */
11938     while (LoadGameOneMove(EndOfFile)) {
11939       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11940       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11941     }
11942
11943     /* rewind to the start of the game */
11944     currentMove = backwardMostMove;
11945
11946     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11947
11948     if (oldGameMode == AnalyzeFile ||
11949         oldGameMode == AnalyzeMode) {
11950       AnalyzeFileEvent();
11951     }
11952
11953     if (!matchMode && pos >= 0) {
11954         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11955     } else
11956     if (matchMode || appData.timeDelay == 0) {
11957       ToEndEvent();
11958     } else if (appData.timeDelay > 0) {
11959       AutoPlayGameLoop();
11960     }
11961
11962     if (appData.debugMode)
11963         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11964
11965     loadFlag = 0; /* [HGM] true game starts */
11966     return TRUE;
11967 }
11968
11969 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11970 int
11971 ReloadPosition(offset)
11972      int offset;
11973 {
11974     int positionNumber = lastLoadPositionNumber + offset;
11975     if (lastLoadPositionFP == NULL) {
11976         DisplayError(_("No position has been loaded yet"), 0);
11977         return FALSE;
11978     }
11979     if (positionNumber <= 0) {
11980         DisplayError(_("Can't back up any further"), 0);
11981         return FALSE;
11982     }
11983     return LoadPosition(lastLoadPositionFP, positionNumber,
11984                         lastLoadPositionTitle);
11985 }
11986
11987 /* Load the nth position from the given file */
11988 int
11989 LoadPositionFromFile(filename, n, title)
11990      char *filename;
11991      int n;
11992      char *title;
11993 {
11994     FILE *f;
11995     char buf[MSG_SIZ];
11996
11997     if (strcmp(filename, "-") == 0) {
11998         return LoadPosition(stdin, n, "stdin");
11999     } else {
12000         f = fopen(filename, "rb");
12001         if (f == NULL) {
12002             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12003             DisplayError(buf, errno);
12004             return FALSE;
12005         } else {
12006             return LoadPosition(f, n, title);
12007         }
12008     }
12009 }
12010
12011 /* Load the nth position from the given open file, and close it */
12012 int
12013 LoadPosition(f, positionNumber, title)
12014      FILE *f;
12015      int positionNumber;
12016      char *title;
12017 {
12018     char *p, line[MSG_SIZ];
12019     Board initial_position;
12020     int i, j, fenMode, pn;
12021
12022     if (gameMode == Training )
12023         SetTrainingModeOff();
12024
12025     if (gameMode != BeginningOfGame) {
12026         Reset(FALSE, TRUE);
12027     }
12028     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12029         fclose(lastLoadPositionFP);
12030     }
12031     if (positionNumber == 0) positionNumber = 1;
12032     lastLoadPositionFP = f;
12033     lastLoadPositionNumber = positionNumber;
12034     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12035     if (first.pr == NoProc && !appData.noChessProgram) {
12036       StartChessProgram(&first);
12037       InitChessProgram(&first, FALSE);
12038     }
12039     pn = positionNumber;
12040     if (positionNumber < 0) {
12041         /* Negative position number means to seek to that byte offset */
12042         if (fseek(f, -positionNumber, 0) == -1) {
12043             DisplayError(_("Can't seek on position file"), 0);
12044             return FALSE;
12045         };
12046         pn = 1;
12047     } else {
12048         if (fseek(f, 0, 0) == -1) {
12049             if (f == lastLoadPositionFP ?
12050                 positionNumber == lastLoadPositionNumber + 1 :
12051                 positionNumber == 1) {
12052                 pn = 1;
12053             } else {
12054                 DisplayError(_("Can't seek on position file"), 0);
12055                 return FALSE;
12056             }
12057         }
12058     }
12059     /* See if this file is FEN or old-style xboard */
12060     if (fgets(line, MSG_SIZ, f) == NULL) {
12061         DisplayError(_("Position not found in file"), 0);
12062         return FALSE;
12063     }
12064     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12065     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12066
12067     if (pn >= 2) {
12068         if (fenMode || line[0] == '#') pn--;
12069         while (pn > 0) {
12070             /* skip positions before number pn */
12071             if (fgets(line, MSG_SIZ, f) == NULL) {
12072                 Reset(TRUE, TRUE);
12073                 DisplayError(_("Position not found in file"), 0);
12074                 return FALSE;
12075             }
12076             if (fenMode || line[0] == '#') pn--;
12077         }
12078     }
12079
12080     if (fenMode) {
12081         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12082             DisplayError(_("Bad FEN position in file"), 0);
12083             return FALSE;
12084         }
12085     } else {
12086         (void) fgets(line, MSG_SIZ, f);
12087         (void) fgets(line, MSG_SIZ, f);
12088
12089         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12090             (void) fgets(line, MSG_SIZ, f);
12091             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12092                 if (*p == ' ')
12093                   continue;
12094                 initial_position[i][j++] = CharToPiece(*p);
12095             }
12096         }
12097
12098         blackPlaysFirst = FALSE;
12099         if (!feof(f)) {
12100             (void) fgets(line, MSG_SIZ, f);
12101             if (strncmp(line, "black", strlen("black"))==0)
12102               blackPlaysFirst = TRUE;
12103         }
12104     }
12105     startedFromSetupPosition = TRUE;
12106
12107     CopyBoard(boards[0], initial_position);
12108     if (blackPlaysFirst) {
12109         currentMove = forwardMostMove = backwardMostMove = 1;
12110         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12111         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12112         CopyBoard(boards[1], initial_position);
12113         DisplayMessage("", _("Black to play"));
12114     } else {
12115         currentMove = forwardMostMove = backwardMostMove = 0;
12116         DisplayMessage("", _("White to play"));
12117     }
12118     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12119     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12120         SendToProgram("force\n", &first);
12121         SendBoard(&first, forwardMostMove);
12122     }
12123     if (appData.debugMode) {
12124 int i, j;
12125   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12126   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12127         fprintf(debugFP, "Load Position\n");
12128     }
12129
12130     if (positionNumber > 1) {
12131       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12132         DisplayTitle(line);
12133     } else {
12134         DisplayTitle(title);
12135     }
12136     gameMode = EditGame;
12137     ModeHighlight();
12138     ResetClocks();
12139     timeRemaining[0][1] = whiteTimeRemaining;
12140     timeRemaining[1][1] = blackTimeRemaining;
12141     DrawPosition(FALSE, boards[currentMove]);
12142
12143     return TRUE;
12144 }
12145
12146
12147 void
12148 CopyPlayerNameIntoFileName(dest, src)
12149      char **dest, *src;
12150 {
12151     while (*src != NULLCHAR && *src != ',') {
12152         if (*src == ' ') {
12153             *(*dest)++ = '_';
12154             src++;
12155         } else {
12156             *(*dest)++ = *src++;
12157         }
12158     }
12159 }
12160
12161 char *DefaultFileName(ext)
12162      char *ext;
12163 {
12164     static char def[MSG_SIZ];
12165     char *p;
12166
12167     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12168         p = def;
12169         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12170         *p++ = '-';
12171         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12172         *p++ = '.';
12173         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12174     } else {
12175         def[0] = NULLCHAR;
12176     }
12177     return def;
12178 }
12179
12180 /* Save the current game to the given file */
12181 int
12182 SaveGameToFile(filename, append)
12183      char *filename;
12184      int append;
12185 {
12186     FILE *f;
12187     char buf[MSG_SIZ];
12188     int result, i, t,tot=0;
12189
12190     if (strcmp(filename, "-") == 0) {
12191         return SaveGame(stdout, 0, NULL);
12192     } else {
12193         for(i=0; i<10; i++) { // upto 10 tries
12194              f = fopen(filename, append ? "a" : "w");
12195              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12196              if(f || errno != 13) break;
12197              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12198              tot += t;
12199         }
12200         if (f == NULL) {
12201             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12202             DisplayError(buf, errno);
12203             return FALSE;
12204         } else {
12205             safeStrCpy(buf, lastMsg, MSG_SIZ);
12206             DisplayMessage(_("Waiting for access to save file"), "");
12207             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12208             DisplayMessage(_("Saving game"), "");
12209             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
12210             result = SaveGame(f, 0, NULL);
12211             DisplayMessage(buf, "");
12212             return result;
12213         }
12214     }
12215 }
12216
12217 char *
12218 SavePart(str)
12219      char *str;
12220 {
12221     static char buf[MSG_SIZ];
12222     char *p;
12223
12224     p = strchr(str, ' ');
12225     if (p == NULL) return str;
12226     strncpy(buf, str, p - str);
12227     buf[p - str] = NULLCHAR;
12228     return buf;
12229 }
12230
12231 #define PGN_MAX_LINE 75
12232
12233 #define PGN_SIDE_WHITE  0
12234 #define PGN_SIDE_BLACK  1
12235
12236 /* [AS] */
12237 static int FindFirstMoveOutOfBook( int side )
12238 {
12239     int result = -1;
12240
12241     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12242         int index = backwardMostMove;
12243         int has_book_hit = 0;
12244
12245         if( (index % 2) != side ) {
12246             index++;
12247         }
12248
12249         while( index < forwardMostMove ) {
12250             /* Check to see if engine is in book */
12251             int depth = pvInfoList[index].depth;
12252             int score = pvInfoList[index].score;
12253             int in_book = 0;
12254
12255             if( depth <= 2 ) {
12256                 in_book = 1;
12257             }
12258             else if( score == 0 && depth == 63 ) {
12259                 in_book = 1; /* Zappa */
12260             }
12261             else if( score == 2 && depth == 99 ) {
12262                 in_book = 1; /* Abrok */
12263             }
12264
12265             has_book_hit += in_book;
12266
12267             if( ! in_book ) {
12268                 result = index;
12269
12270                 break;
12271             }
12272
12273             index += 2;
12274         }
12275     }
12276
12277     return result;
12278 }
12279
12280 /* [AS] */
12281 void GetOutOfBookInfo( char * buf )
12282 {
12283     int oob[2];
12284     int i;
12285     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12286
12287     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12288     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12289
12290     *buf = '\0';
12291
12292     if( oob[0] >= 0 || oob[1] >= 0 ) {
12293         for( i=0; i<2; i++ ) {
12294             int idx = oob[i];
12295
12296             if( idx >= 0 ) {
12297                 if( i > 0 && oob[0] >= 0 ) {
12298                     strcat( buf, "   " );
12299                 }
12300
12301                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12302                 sprintf( buf+strlen(buf), "%s%.2f",
12303                     pvInfoList[idx].score >= 0 ? "+" : "",
12304                     pvInfoList[idx].score / 100.0 );
12305             }
12306         }
12307     }
12308 }
12309
12310 /* Save game in PGN style and close the file */
12311 int
12312 SaveGamePGN(f)
12313      FILE *f;
12314 {
12315     int i, offset, linelen, newblock;
12316     time_t tm;
12317 //    char *movetext;
12318     char numtext[32];
12319     int movelen, numlen, blank;
12320     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12321
12322     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12323
12324     tm = time((time_t *) NULL);
12325
12326     PrintPGNTags(f, &gameInfo);
12327
12328     if (backwardMostMove > 0 || startedFromSetupPosition) {
12329         char *fen = PositionToFEN(backwardMostMove, NULL);
12330         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12331         fprintf(f, "\n{--------------\n");
12332         PrintPosition(f, backwardMostMove);
12333         fprintf(f, "--------------}\n");
12334         free(fen);
12335     }
12336     else {
12337         /* [AS] Out of book annotation */
12338         if( appData.saveOutOfBookInfo ) {
12339             char buf[64];
12340
12341             GetOutOfBookInfo( buf );
12342
12343             if( buf[0] != '\0' ) {
12344                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12345             }
12346         }
12347
12348         fprintf(f, "\n");
12349     }
12350
12351     i = backwardMostMove;
12352     linelen = 0;
12353     newblock = TRUE;
12354
12355     while (i < forwardMostMove) {
12356         /* Print comments preceding this move */
12357         if (commentList[i] != NULL) {
12358             if (linelen > 0) fprintf(f, "\n");
12359             fprintf(f, "%s", commentList[i]);
12360             linelen = 0;
12361             newblock = TRUE;
12362         }
12363
12364         /* Format move number */
12365         if ((i % 2) == 0)
12366           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12367         else
12368           if (newblock)
12369             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12370           else
12371             numtext[0] = NULLCHAR;
12372
12373         numlen = strlen(numtext);
12374         newblock = FALSE;
12375
12376         /* Print move number */
12377         blank = linelen > 0 && numlen > 0;
12378         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12379             fprintf(f, "\n");
12380             linelen = 0;
12381             blank = 0;
12382         }
12383         if (blank) {
12384             fprintf(f, " ");
12385             linelen++;
12386         }
12387         fprintf(f, "%s", numtext);
12388         linelen += numlen;
12389
12390         /* Get move */
12391         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12392         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12393
12394         /* Print move */
12395         blank = linelen > 0 && movelen > 0;
12396         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12397             fprintf(f, "\n");
12398             linelen = 0;
12399             blank = 0;
12400         }
12401         if (blank) {
12402             fprintf(f, " ");
12403             linelen++;
12404         }
12405         fprintf(f, "%s", move_buffer);
12406         linelen += movelen;
12407
12408         /* [AS] Add PV info if present */
12409         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12410             /* [HGM] add time */
12411             char buf[MSG_SIZ]; int seconds;
12412
12413             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12414
12415             if( seconds <= 0)
12416               buf[0] = 0;
12417             else
12418               if( seconds < 30 )
12419                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12420               else
12421                 {
12422                   seconds = (seconds + 4)/10; // round to full seconds
12423                   if( seconds < 60 )
12424                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12425                   else
12426                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12427                 }
12428
12429             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12430                       pvInfoList[i].score >= 0 ? "+" : "",
12431                       pvInfoList[i].score / 100.0,
12432                       pvInfoList[i].depth,
12433                       buf );
12434
12435             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12436
12437             /* Print score/depth */
12438             blank = linelen > 0 && movelen > 0;
12439             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12440                 fprintf(f, "\n");
12441                 linelen = 0;
12442                 blank = 0;
12443             }
12444             if (blank) {
12445                 fprintf(f, " ");
12446                 linelen++;
12447             }
12448             fprintf(f, "%s", move_buffer);
12449             linelen += movelen;
12450         }
12451
12452         i++;
12453     }
12454
12455     /* Start a new line */
12456     if (linelen > 0) fprintf(f, "\n");
12457
12458     /* Print comments after last move */
12459     if (commentList[i] != NULL) {
12460         fprintf(f, "%s\n", commentList[i]);
12461     }
12462
12463     /* Print result */
12464     if (gameInfo.resultDetails != NULL &&
12465         gameInfo.resultDetails[0] != NULLCHAR) {
12466         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12467                 PGNResult(gameInfo.result));
12468     } else {
12469         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12470     }
12471
12472     fclose(f);
12473     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12474     return TRUE;
12475 }
12476
12477 /* Save game in old style and close the file */
12478 int
12479 SaveGameOldStyle(f)
12480      FILE *f;
12481 {
12482     int i, offset;
12483     time_t tm;
12484
12485     tm = time((time_t *) NULL);
12486
12487     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12488     PrintOpponents(f);
12489
12490     if (backwardMostMove > 0 || startedFromSetupPosition) {
12491         fprintf(f, "\n[--------------\n");
12492         PrintPosition(f, backwardMostMove);
12493         fprintf(f, "--------------]\n");
12494     } else {
12495         fprintf(f, "\n");
12496     }
12497
12498     i = backwardMostMove;
12499     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12500
12501     while (i < forwardMostMove) {
12502         if (commentList[i] != NULL) {
12503             fprintf(f, "[%s]\n", commentList[i]);
12504         }
12505
12506         if ((i % 2) == 1) {
12507             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12508             i++;
12509         } else {
12510             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12511             i++;
12512             if (commentList[i] != NULL) {
12513                 fprintf(f, "\n");
12514                 continue;
12515             }
12516             if (i >= forwardMostMove) {
12517                 fprintf(f, "\n");
12518                 break;
12519             }
12520             fprintf(f, "%s\n", parseList[i]);
12521             i++;
12522         }
12523     }
12524
12525     if (commentList[i] != NULL) {
12526         fprintf(f, "[%s]\n", commentList[i]);
12527     }
12528
12529     /* This isn't really the old style, but it's close enough */
12530     if (gameInfo.resultDetails != NULL &&
12531         gameInfo.resultDetails[0] != NULLCHAR) {
12532         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12533                 gameInfo.resultDetails);
12534     } else {
12535         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12536     }
12537
12538     fclose(f);
12539     return TRUE;
12540 }
12541
12542 /* Save the current game to open file f and close the file */
12543 int
12544 SaveGame(f, dummy, dummy2)
12545      FILE *f;
12546      int dummy;
12547      char *dummy2;
12548 {
12549     if (gameMode == EditPosition) EditPositionDone(TRUE);
12550     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12551     if (appData.oldSaveStyle)
12552       return SaveGameOldStyle(f);
12553     else
12554       return SaveGamePGN(f);
12555 }
12556
12557 /* Save the current position to the given file */
12558 int
12559 SavePositionToFile(filename)
12560      char *filename;
12561 {
12562     FILE *f;
12563     char buf[MSG_SIZ];
12564
12565     if (strcmp(filename, "-") == 0) {
12566         return SavePosition(stdout, 0, NULL);
12567     } else {
12568         f = fopen(filename, "a");
12569         if (f == NULL) {
12570             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12571             DisplayError(buf, errno);
12572             return FALSE;
12573         } else {
12574             safeStrCpy(buf, lastMsg, MSG_SIZ);
12575             DisplayMessage(_("Waiting for access to save file"), "");
12576             flock(fileno(f), LOCK_EX); // [HGM] lock
12577             DisplayMessage(_("Saving position"), "");
12578             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12579             SavePosition(f, 0, NULL);
12580             DisplayMessage(buf, "");
12581             return TRUE;
12582         }
12583     }
12584 }
12585
12586 /* Save the current position to the given open file and close the file */
12587 int
12588 SavePosition(f, dummy, dummy2)
12589      FILE *f;
12590      int dummy;
12591      char *dummy2;
12592 {
12593     time_t tm;
12594     char *fen;
12595
12596     if (gameMode == EditPosition) EditPositionDone(TRUE);
12597     if (appData.oldSaveStyle) {
12598         tm = time((time_t *) NULL);
12599
12600         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12601         PrintOpponents(f);
12602         fprintf(f, "[--------------\n");
12603         PrintPosition(f, currentMove);
12604         fprintf(f, "--------------]\n");
12605     } else {
12606         fen = PositionToFEN(currentMove, NULL);
12607         fprintf(f, "%s\n", fen);
12608         free(fen);
12609     }
12610     fclose(f);
12611     return TRUE;
12612 }
12613
12614 void
12615 ReloadCmailMsgEvent(unregister)
12616      int unregister;
12617 {
12618 #if !WIN32
12619     static char *inFilename = NULL;
12620     static char *outFilename;
12621     int i;
12622     struct stat inbuf, outbuf;
12623     int status;
12624
12625     /* Any registered moves are unregistered if unregister is set, */
12626     /* i.e. invoked by the signal handler */
12627     if (unregister) {
12628         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12629             cmailMoveRegistered[i] = FALSE;
12630             if (cmailCommentList[i] != NULL) {
12631                 free(cmailCommentList[i]);
12632                 cmailCommentList[i] = NULL;
12633             }
12634         }
12635         nCmailMovesRegistered = 0;
12636     }
12637
12638     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12639         cmailResult[i] = CMAIL_NOT_RESULT;
12640     }
12641     nCmailResults = 0;
12642
12643     if (inFilename == NULL) {
12644         /* Because the filenames are static they only get malloced once  */
12645         /* and they never get freed                                      */
12646         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12647         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12648
12649         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12650         sprintf(outFilename, "%s.out", appData.cmailGameName);
12651     }
12652
12653     status = stat(outFilename, &outbuf);
12654     if (status < 0) {
12655         cmailMailedMove = FALSE;
12656     } else {
12657         status = stat(inFilename, &inbuf);
12658         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12659     }
12660
12661     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12662        counts the games, notes how each one terminated, etc.
12663
12664        It would be nice to remove this kludge and instead gather all
12665        the information while building the game list.  (And to keep it
12666        in the game list nodes instead of having a bunch of fixed-size
12667        parallel arrays.)  Note this will require getting each game's
12668        termination from the PGN tags, as the game list builder does
12669        not process the game moves.  --mann
12670        */
12671     cmailMsgLoaded = TRUE;
12672     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12673
12674     /* Load first game in the file or popup game menu */
12675     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12676
12677 #endif /* !WIN32 */
12678     return;
12679 }
12680
12681 int
12682 RegisterMove()
12683 {
12684     FILE *f;
12685     char string[MSG_SIZ];
12686
12687     if (   cmailMailedMove
12688         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12689         return TRUE;            /* Allow free viewing  */
12690     }
12691
12692     /* Unregister move to ensure that we don't leave RegisterMove        */
12693     /* with the move registered when the conditions for registering no   */
12694     /* longer hold                                                       */
12695     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12696         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12697         nCmailMovesRegistered --;
12698
12699         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12700           {
12701               free(cmailCommentList[lastLoadGameNumber - 1]);
12702               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12703           }
12704     }
12705
12706     if (cmailOldMove == -1) {
12707         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12708         return FALSE;
12709     }
12710
12711     if (currentMove > cmailOldMove + 1) {
12712         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12713         return FALSE;
12714     }
12715
12716     if (currentMove < cmailOldMove) {
12717         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12718         return FALSE;
12719     }
12720
12721     if (forwardMostMove > currentMove) {
12722         /* Silently truncate extra moves */
12723         TruncateGame();
12724     }
12725
12726     if (   (currentMove == cmailOldMove + 1)
12727         || (   (currentMove == cmailOldMove)
12728             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12729                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12730         if (gameInfo.result != GameUnfinished) {
12731             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12732         }
12733
12734         if (commentList[currentMove] != NULL) {
12735             cmailCommentList[lastLoadGameNumber - 1]
12736               = StrSave(commentList[currentMove]);
12737         }
12738         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12739
12740         if (appData.debugMode)
12741           fprintf(debugFP, "Saving %s for game %d\n",
12742                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12743
12744         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12745
12746         f = fopen(string, "w");
12747         if (appData.oldSaveStyle) {
12748             SaveGameOldStyle(f); /* also closes the file */
12749
12750             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12751             f = fopen(string, "w");
12752             SavePosition(f, 0, NULL); /* also closes the file */
12753         } else {
12754             fprintf(f, "{--------------\n");
12755             PrintPosition(f, currentMove);
12756             fprintf(f, "--------------}\n\n");
12757
12758             SaveGame(f, 0, NULL); /* also closes the file*/
12759         }
12760
12761         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12762         nCmailMovesRegistered ++;
12763     } else if (nCmailGames == 1) {
12764         DisplayError(_("You have not made a move yet"), 0);
12765         return FALSE;
12766     }
12767
12768     return TRUE;
12769 }
12770
12771 void
12772 MailMoveEvent()
12773 {
12774 #if !WIN32
12775     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12776     FILE *commandOutput;
12777     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12778     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12779     int nBuffers;
12780     int i;
12781     int archived;
12782     char *arcDir;
12783
12784     if (! cmailMsgLoaded) {
12785         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12786         return;
12787     }
12788
12789     if (nCmailGames == nCmailResults) {
12790         DisplayError(_("No unfinished games"), 0);
12791         return;
12792     }
12793
12794 #if CMAIL_PROHIBIT_REMAIL
12795     if (cmailMailedMove) {
12796       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);
12797         DisplayError(msg, 0);
12798         return;
12799     }
12800 #endif
12801
12802     if (! (cmailMailedMove || RegisterMove())) return;
12803
12804     if (   cmailMailedMove
12805         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12806       snprintf(string, MSG_SIZ, partCommandString,
12807                appData.debugMode ? " -v" : "", appData.cmailGameName);
12808         commandOutput = popen(string, "r");
12809
12810         if (commandOutput == NULL) {
12811             DisplayError(_("Failed to invoke cmail"), 0);
12812         } else {
12813             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12814                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12815             }
12816             if (nBuffers > 1) {
12817                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12818                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12819                 nBytes = MSG_SIZ - 1;
12820             } else {
12821                 (void) memcpy(msg, buffer, nBytes);
12822             }
12823             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12824
12825             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12826                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12827
12828                 archived = TRUE;
12829                 for (i = 0; i < nCmailGames; i ++) {
12830                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12831                         archived = FALSE;
12832                     }
12833                 }
12834                 if (   archived
12835                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12836                         != NULL)) {
12837                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12838                            arcDir,
12839                            appData.cmailGameName,
12840                            gameInfo.date);
12841                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12842                     cmailMsgLoaded = FALSE;
12843                 }
12844             }
12845
12846             DisplayInformation(msg);
12847             pclose(commandOutput);
12848         }
12849     } else {
12850         if ((*cmailMsg) != '\0') {
12851             DisplayInformation(cmailMsg);
12852         }
12853     }
12854
12855     return;
12856 #endif /* !WIN32 */
12857 }
12858
12859 char *
12860 CmailMsg()
12861 {
12862 #if WIN32
12863     return NULL;
12864 #else
12865     int  prependComma = 0;
12866     char number[5];
12867     char string[MSG_SIZ];       /* Space for game-list */
12868     int  i;
12869
12870     if (!cmailMsgLoaded) return "";
12871
12872     if (cmailMailedMove) {
12873       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12874     } else {
12875         /* Create a list of games left */
12876       snprintf(string, MSG_SIZ, "[");
12877         for (i = 0; i < nCmailGames; i ++) {
12878             if (! (   cmailMoveRegistered[i]
12879                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12880                 if (prependComma) {
12881                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12882                 } else {
12883                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12884                     prependComma = 1;
12885                 }
12886
12887                 strcat(string, number);
12888             }
12889         }
12890         strcat(string, "]");
12891
12892         if (nCmailMovesRegistered + nCmailResults == 0) {
12893             switch (nCmailGames) {
12894               case 1:
12895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12896                 break;
12897
12898               case 2:
12899                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12900                 break;
12901
12902               default:
12903                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12904                          nCmailGames);
12905                 break;
12906             }
12907         } else {
12908             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12909               case 1:
12910                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12911                          string);
12912                 break;
12913
12914               case 0:
12915                 if (nCmailResults == nCmailGames) {
12916                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12917                 } else {
12918                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12919                 }
12920                 break;
12921
12922               default:
12923                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12924                          string);
12925             }
12926         }
12927     }
12928     return cmailMsg;
12929 #endif /* WIN32 */
12930 }
12931
12932 void
12933 ResetGameEvent()
12934 {
12935     if (gameMode == Training)
12936       SetTrainingModeOff();
12937
12938     Reset(TRUE, TRUE);
12939     cmailMsgLoaded = FALSE;
12940     if (appData.icsActive) {
12941       SendToICS(ics_prefix);
12942       SendToICS("refresh\n");
12943     }
12944 }
12945
12946 void
12947 ExitEvent(status)
12948      int status;
12949 {
12950     exiting++;
12951     if (exiting > 2) {
12952       /* Give up on clean exit */
12953       exit(status);
12954     }
12955     if (exiting > 1) {
12956       /* Keep trying for clean exit */
12957       return;
12958     }
12959
12960     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12961
12962     if (telnetISR != NULL) {
12963       RemoveInputSource(telnetISR);
12964     }
12965     if (icsPR != NoProc) {
12966       DestroyChildProcess(icsPR, TRUE);
12967     }
12968
12969     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12970     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12971
12972     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12973     /* make sure this other one finishes before killing it!                  */
12974     if(endingGame) { int count = 0;
12975         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12976         while(endingGame && count++ < 10) DoSleep(1);
12977         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12978     }
12979
12980     /* Kill off chess programs */
12981     if (first.pr != NoProc) {
12982         ExitAnalyzeMode();
12983
12984         DoSleep( appData.delayBeforeQuit );
12985         SendToProgram("quit\n", &first);
12986         DoSleep( appData.delayAfterQuit );
12987         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12988     }
12989     if (second.pr != NoProc) {
12990         DoSleep( appData.delayBeforeQuit );
12991         SendToProgram("quit\n", &second);
12992         DoSleep( appData.delayAfterQuit );
12993         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12994     }
12995     if (first.isr != NULL) {
12996         RemoveInputSource(first.isr);
12997     }
12998     if (second.isr != NULL) {
12999         RemoveInputSource(second.isr);
13000     }
13001
13002     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13003     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13004
13005     ShutDownFrontEnd();
13006     exit(status);
13007 }
13008
13009 void
13010 PauseEvent()
13011 {
13012     if (appData.debugMode)
13013         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13014     if (pausing) {
13015         pausing = FALSE;
13016         ModeHighlight();
13017         if (gameMode == MachinePlaysWhite ||
13018             gameMode == MachinePlaysBlack) {
13019             StartClocks();
13020         } else {
13021             DisplayBothClocks();
13022         }
13023         if (gameMode == PlayFromGameFile) {
13024             if (appData.timeDelay >= 0)
13025                 AutoPlayGameLoop();
13026         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13027             Reset(FALSE, TRUE);
13028             SendToICS(ics_prefix);
13029             SendToICS("refresh\n");
13030         } else if (currentMove < forwardMostMove) {
13031             ForwardInner(forwardMostMove);
13032         }
13033         pauseExamInvalid = FALSE;
13034     } else {
13035         switch (gameMode) {
13036           default:
13037             return;
13038           case IcsExamining:
13039             pauseExamForwardMostMove = forwardMostMove;
13040             pauseExamInvalid = FALSE;
13041             /* fall through */
13042           case IcsObserving:
13043           case IcsPlayingWhite:
13044           case IcsPlayingBlack:
13045             pausing = TRUE;
13046             ModeHighlight();
13047             return;
13048           case PlayFromGameFile:
13049             (void) StopLoadGameTimer();
13050             pausing = TRUE;
13051             ModeHighlight();
13052             break;
13053           case BeginningOfGame:
13054             if (appData.icsActive) return;
13055             /* else fall through */
13056           case MachinePlaysWhite:
13057           case MachinePlaysBlack:
13058           case TwoMachinesPlay:
13059             if (forwardMostMove == 0)
13060               return;           /* don't pause if no one has moved */
13061             if ((gameMode == MachinePlaysWhite &&
13062                  !WhiteOnMove(forwardMostMove)) ||
13063                 (gameMode == MachinePlaysBlack &&
13064                  WhiteOnMove(forwardMostMove))) {
13065                 StopClocks();
13066             }
13067             pausing = TRUE;
13068             ModeHighlight();
13069             break;
13070         }
13071     }
13072 }
13073
13074 void
13075 EditCommentEvent()
13076 {
13077     char title[MSG_SIZ];
13078
13079     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13080       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13081     } else {
13082       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13083                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13084                parseList[currentMove - 1]);
13085     }
13086
13087     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13088 }
13089
13090
13091 void
13092 EditTagsEvent()
13093 {
13094     char *tags = PGNTags(&gameInfo);
13095     bookUp = FALSE;
13096     EditTagsPopUp(tags, NULL);
13097     free(tags);
13098 }
13099
13100 void
13101 AnalyzeModeEvent()
13102 {
13103     if (appData.noChessProgram || gameMode == AnalyzeMode)
13104       return;
13105
13106     if (gameMode != AnalyzeFile) {
13107         if (!appData.icsEngineAnalyze) {
13108                EditGameEvent();
13109                if (gameMode != EditGame) return;
13110         }
13111         ResurrectChessProgram();
13112         SendToProgram("analyze\n", &first);
13113         first.analyzing = TRUE;
13114         /*first.maybeThinking = TRUE;*/
13115         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13116         EngineOutputPopUp();
13117     }
13118     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13119     pausing = FALSE;
13120     ModeHighlight();
13121     SetGameInfo();
13122
13123     StartAnalysisClock();
13124     GetTimeMark(&lastNodeCountTime);
13125     lastNodeCount = 0;
13126 }
13127
13128 void
13129 AnalyzeFileEvent()
13130 {
13131     if (appData.noChessProgram || gameMode == AnalyzeFile)
13132       return;
13133
13134     if (gameMode != AnalyzeMode) {
13135         EditGameEvent();
13136         if (gameMode != EditGame) return;
13137         ResurrectChessProgram();
13138         SendToProgram("analyze\n", &first);
13139         first.analyzing = TRUE;
13140         /*first.maybeThinking = TRUE;*/
13141         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13142         EngineOutputPopUp();
13143     }
13144     gameMode = AnalyzeFile;
13145     pausing = FALSE;
13146     ModeHighlight();
13147     SetGameInfo();
13148
13149     StartAnalysisClock();
13150     GetTimeMark(&lastNodeCountTime);
13151     lastNodeCount = 0;
13152     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13153 }
13154
13155 void
13156 MachineWhiteEvent()
13157 {
13158     char buf[MSG_SIZ];
13159     char *bookHit = NULL;
13160
13161     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13162       return;
13163
13164
13165     if (gameMode == PlayFromGameFile ||
13166         gameMode == TwoMachinesPlay  ||
13167         gameMode == Training         ||
13168         gameMode == AnalyzeMode      ||
13169         gameMode == EndOfGame)
13170         EditGameEvent();
13171
13172     if (gameMode == EditPosition)
13173         EditPositionDone(TRUE);
13174
13175     if (!WhiteOnMove(currentMove)) {
13176         DisplayError(_("It is not White's turn"), 0);
13177         return;
13178     }
13179
13180     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13181       ExitAnalyzeMode();
13182
13183     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13184         gameMode == AnalyzeFile)
13185         TruncateGame();
13186
13187     ResurrectChessProgram();    /* in case it isn't running */
13188     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13189         gameMode = MachinePlaysWhite;
13190         ResetClocks();
13191     } else
13192     gameMode = MachinePlaysWhite;
13193     pausing = FALSE;
13194     ModeHighlight();
13195     SetGameInfo();
13196     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13197     DisplayTitle(buf);
13198     if (first.sendName) {
13199       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13200       SendToProgram(buf, &first);
13201     }
13202     if (first.sendTime) {
13203       if (first.useColors) {
13204         SendToProgram("black\n", &first); /*gnu kludge*/
13205       }
13206       SendTimeRemaining(&first, TRUE);
13207     }
13208     if (first.useColors) {
13209       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13210     }
13211     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13212     SetMachineThinkingEnables();
13213     first.maybeThinking = TRUE;
13214     StartClocks();
13215     firstMove = FALSE;
13216
13217     if (appData.autoFlipView && !flipView) {
13218       flipView = !flipView;
13219       DrawPosition(FALSE, NULL);
13220       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13221     }
13222
13223     if(bookHit) { // [HGM] book: simulate book reply
13224         static char bookMove[MSG_SIZ]; // a bit generous?
13225
13226         programStats.nodes = programStats.depth = programStats.time =
13227         programStats.score = programStats.got_only_move = 0;
13228         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13229
13230         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13231         strcat(bookMove, bookHit);
13232         HandleMachineMove(bookMove, &first);
13233     }
13234 }
13235
13236 void
13237 MachineBlackEvent()
13238 {
13239   char buf[MSG_SIZ];
13240   char *bookHit = NULL;
13241
13242     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13243         return;
13244
13245
13246     if (gameMode == PlayFromGameFile ||
13247         gameMode == TwoMachinesPlay  ||
13248         gameMode == Training         ||
13249         gameMode == AnalyzeMode      ||
13250         gameMode == EndOfGame)
13251         EditGameEvent();
13252
13253     if (gameMode == EditPosition)
13254         EditPositionDone(TRUE);
13255
13256     if (WhiteOnMove(currentMove)) {
13257         DisplayError(_("It is not Black's turn"), 0);
13258         return;
13259     }
13260
13261     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13262       ExitAnalyzeMode();
13263
13264     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13265         gameMode == AnalyzeFile)
13266         TruncateGame();
13267
13268     ResurrectChessProgram();    /* in case it isn't running */
13269     gameMode = MachinePlaysBlack;
13270     pausing = FALSE;
13271     ModeHighlight();
13272     SetGameInfo();
13273     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13274     DisplayTitle(buf);
13275     if (first.sendName) {
13276       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13277       SendToProgram(buf, &first);
13278     }
13279     if (first.sendTime) {
13280       if (first.useColors) {
13281         SendToProgram("white\n", &first); /*gnu kludge*/
13282       }
13283       SendTimeRemaining(&first, FALSE);
13284     }
13285     if (first.useColors) {
13286       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13287     }
13288     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13289     SetMachineThinkingEnables();
13290     first.maybeThinking = TRUE;
13291     StartClocks();
13292
13293     if (appData.autoFlipView && flipView) {
13294       flipView = !flipView;
13295       DrawPosition(FALSE, NULL);
13296       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13297     }
13298     if(bookHit) { // [HGM] book: simulate book reply
13299         static char bookMove[MSG_SIZ]; // a bit generous?
13300
13301         programStats.nodes = programStats.depth = programStats.time =
13302         programStats.score = programStats.got_only_move = 0;
13303         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13304
13305         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13306         strcat(bookMove, bookHit);
13307         HandleMachineMove(bookMove, &first);
13308     }
13309 }
13310
13311
13312 void
13313 DisplayTwoMachinesTitle()
13314 {
13315     char buf[MSG_SIZ];
13316     if (appData.matchGames > 0) {
13317         if(appData.tourneyFile[0]) {
13318           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13319                    gameInfo.white, gameInfo.black,
13320                    nextGame+1, appData.matchGames+1,
13321                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13322         } else 
13323         if (first.twoMachinesColor[0] == 'w') {
13324           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13325                    gameInfo.white, gameInfo.black,
13326                    first.matchWins, second.matchWins,
13327                    matchGame - 1 - (first.matchWins + second.matchWins));
13328         } else {
13329           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13330                    gameInfo.white, gameInfo.black,
13331                    second.matchWins, first.matchWins,
13332                    matchGame - 1 - (first.matchWins + second.matchWins));
13333         }
13334     } else {
13335       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13336     }
13337     DisplayTitle(buf);
13338 }
13339
13340 void
13341 SettingsMenuIfReady()
13342 {
13343   if (second.lastPing != second.lastPong) {
13344     DisplayMessage("", _("Waiting for second chess program"));
13345     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13346     return;
13347   }
13348   ThawUI();
13349   DisplayMessage("", "");
13350   SettingsPopUp(&second);
13351 }
13352
13353 int
13354 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13355 {
13356     char buf[MSG_SIZ];
13357     if (cps->pr == NoProc) {
13358         StartChessProgram(cps);
13359         if (cps->protocolVersion == 1) {
13360           retry();
13361         } else {
13362           /* kludge: allow timeout for initial "feature" command */
13363           FreezeUI();
13364           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13365           DisplayMessage("", buf);
13366           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13367         }
13368         return 1;
13369     }
13370     return 0;
13371 }
13372
13373 void
13374 TwoMachinesEvent P((void))
13375 {
13376     int i;
13377     char buf[MSG_SIZ];
13378     ChessProgramState *onmove;
13379     char *bookHit = NULL;
13380     static int stalling = 0;
13381     TimeMark now;
13382     long wait;
13383
13384     if (appData.noChessProgram) return;
13385
13386     switch (gameMode) {
13387       case TwoMachinesPlay:
13388         return;
13389       case MachinePlaysWhite:
13390       case MachinePlaysBlack:
13391         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13392             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13393             return;
13394         }
13395         /* fall through */
13396       case BeginningOfGame:
13397       case PlayFromGameFile:
13398       case EndOfGame:
13399         EditGameEvent();
13400         if (gameMode != EditGame) return;
13401         break;
13402       case EditPosition:
13403         EditPositionDone(TRUE);
13404         break;
13405       case AnalyzeMode:
13406       case AnalyzeFile:
13407         ExitAnalyzeMode();
13408         break;
13409       case EditGame:
13410       default:
13411         break;
13412     }
13413
13414 //    forwardMostMove = currentMove;
13415     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13416
13417     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13418
13419     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13420     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13421       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13422       return;
13423     }
13424     if(!stalling) {
13425       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13426       SendToProgram("force\n", &second);
13427       stalling = 1;
13428       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13429       return;
13430     }
13431     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13432     if(appData.matchPause>10000 || appData.matchPause<10)
13433                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13434     wait = SubtractTimeMarks(&now, &pauseStart);
13435     if(wait < appData.matchPause) {
13436         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13437         return;
13438     }
13439     stalling = 0;
13440     DisplayMessage("", "");
13441     if (startedFromSetupPosition) {
13442         SendBoard(&second, backwardMostMove);
13443     if (appData.debugMode) {
13444         fprintf(debugFP, "Two Machines\n");
13445     }
13446     }
13447     for (i = backwardMostMove; i < forwardMostMove; i++) {
13448         SendMoveToProgram(i, &second);
13449     }
13450
13451     gameMode = TwoMachinesPlay;
13452     pausing = FALSE;
13453     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13454     SetGameInfo();
13455     DisplayTwoMachinesTitle();
13456     firstMove = TRUE;
13457     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13458         onmove = &first;
13459     } else {
13460         onmove = &second;
13461     }
13462     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13463     SendToProgram(first.computerString, &first);
13464     if (first.sendName) {
13465       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13466       SendToProgram(buf, &first);
13467     }
13468     SendToProgram(second.computerString, &second);
13469     if (second.sendName) {
13470       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13471       SendToProgram(buf, &second);
13472     }
13473
13474     ResetClocks();
13475     if (!first.sendTime || !second.sendTime) {
13476         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13477         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13478     }
13479     if (onmove->sendTime) {
13480       if (onmove->useColors) {
13481         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13482       }
13483       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13484     }
13485     if (onmove->useColors) {
13486       SendToProgram(onmove->twoMachinesColor, onmove);
13487     }
13488     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13489 //    SendToProgram("go\n", onmove);
13490     onmove->maybeThinking = TRUE;
13491     SetMachineThinkingEnables();
13492
13493     StartClocks();
13494
13495     if(bookHit) { // [HGM] book: simulate book reply
13496         static char bookMove[MSG_SIZ]; // a bit generous?
13497
13498         programStats.nodes = programStats.depth = programStats.time =
13499         programStats.score = programStats.got_only_move = 0;
13500         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13501
13502         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13503         strcat(bookMove, bookHit);
13504         savedMessage = bookMove; // args for deferred call
13505         savedState = onmove;
13506         ScheduleDelayedEvent(DeferredBookMove, 1);
13507     }
13508 }
13509
13510 void
13511 TrainingEvent()
13512 {
13513     if (gameMode == Training) {
13514       SetTrainingModeOff();
13515       gameMode = PlayFromGameFile;
13516       DisplayMessage("", _("Training mode off"));
13517     } else {
13518       gameMode = Training;
13519       animateTraining = appData.animate;
13520
13521       /* make sure we are not already at the end of the game */
13522       if (currentMove < forwardMostMove) {
13523         SetTrainingModeOn();
13524         DisplayMessage("", _("Training mode on"));
13525       } else {
13526         gameMode = PlayFromGameFile;
13527         DisplayError(_("Already at end of game"), 0);
13528       }
13529     }
13530     ModeHighlight();
13531 }
13532
13533 void
13534 IcsClientEvent()
13535 {
13536     if (!appData.icsActive) return;
13537     switch (gameMode) {
13538       case IcsPlayingWhite:
13539       case IcsPlayingBlack:
13540       case IcsObserving:
13541       case IcsIdle:
13542       case BeginningOfGame:
13543       case IcsExamining:
13544         return;
13545
13546       case EditGame:
13547         break;
13548
13549       case EditPosition:
13550         EditPositionDone(TRUE);
13551         break;
13552
13553       case AnalyzeMode:
13554       case AnalyzeFile:
13555         ExitAnalyzeMode();
13556         break;
13557
13558       default:
13559         EditGameEvent();
13560         break;
13561     }
13562
13563     gameMode = IcsIdle;
13564     ModeHighlight();
13565     return;
13566 }
13567
13568
13569 void
13570 EditGameEvent()
13571 {
13572     int i;
13573
13574     switch (gameMode) {
13575       case Training:
13576         SetTrainingModeOff();
13577         break;
13578       case MachinePlaysWhite:
13579       case MachinePlaysBlack:
13580       case BeginningOfGame:
13581         SendToProgram("force\n", &first);
13582         SetUserThinkingEnables();
13583         break;
13584       case PlayFromGameFile:
13585         (void) StopLoadGameTimer();
13586         if (gameFileFP != NULL) {
13587             gameFileFP = NULL;
13588         }
13589         break;
13590       case EditPosition:
13591         EditPositionDone(TRUE);
13592         break;
13593       case AnalyzeMode:
13594       case AnalyzeFile:
13595         ExitAnalyzeMode();
13596         SendToProgram("force\n", &first);
13597         break;
13598       case TwoMachinesPlay:
13599         GameEnds(EndOfFile, NULL, GE_PLAYER);
13600         ResurrectChessProgram();
13601         SetUserThinkingEnables();
13602         break;
13603       case EndOfGame:
13604         ResurrectChessProgram();
13605         break;
13606       case IcsPlayingBlack:
13607       case IcsPlayingWhite:
13608         DisplayError(_("Warning: You are still playing a game"), 0);
13609         break;
13610       case IcsObserving:
13611         DisplayError(_("Warning: You are still observing a game"), 0);
13612         break;
13613       case IcsExamining:
13614         DisplayError(_("Warning: You are still examining a game"), 0);
13615         break;
13616       case IcsIdle:
13617         break;
13618       case EditGame:
13619       default:
13620         return;
13621     }
13622
13623     pausing = FALSE;
13624     StopClocks();
13625     first.offeredDraw = second.offeredDraw = 0;
13626
13627     if (gameMode == PlayFromGameFile) {
13628         whiteTimeRemaining = timeRemaining[0][currentMove];
13629         blackTimeRemaining = timeRemaining[1][currentMove];
13630         DisplayTitle("");
13631     }
13632
13633     if (gameMode == MachinePlaysWhite ||
13634         gameMode == MachinePlaysBlack ||
13635         gameMode == TwoMachinesPlay ||
13636         gameMode == EndOfGame) {
13637         i = forwardMostMove;
13638         while (i > currentMove) {
13639             SendToProgram("undo\n", &first);
13640             i--;
13641         }
13642         if(!adjustedClock) {
13643         whiteTimeRemaining = timeRemaining[0][currentMove];
13644         blackTimeRemaining = timeRemaining[1][currentMove];
13645         DisplayBothClocks();
13646         }
13647         if (whiteFlag || blackFlag) {
13648             whiteFlag = blackFlag = 0;
13649         }
13650         DisplayTitle("");
13651     }
13652
13653     gameMode = EditGame;
13654     ModeHighlight();
13655     SetGameInfo();
13656 }
13657
13658
13659 void
13660 EditPositionEvent()
13661 {
13662     if (gameMode == EditPosition) {
13663         EditGameEvent();
13664         return;
13665     }
13666
13667     EditGameEvent();
13668     if (gameMode != EditGame) return;
13669
13670     gameMode = EditPosition;
13671     ModeHighlight();
13672     SetGameInfo();
13673     if (currentMove > 0)
13674       CopyBoard(boards[0], boards[currentMove]);
13675
13676     blackPlaysFirst = !WhiteOnMove(currentMove);
13677     ResetClocks();
13678     currentMove = forwardMostMove = backwardMostMove = 0;
13679     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13680     DisplayMove(-1);
13681 }
13682
13683 void
13684 ExitAnalyzeMode()
13685 {
13686     /* [DM] icsEngineAnalyze - possible call from other functions */
13687     if (appData.icsEngineAnalyze) {
13688         appData.icsEngineAnalyze = FALSE;
13689
13690         DisplayMessage("",_("Close ICS engine analyze..."));
13691     }
13692     if (first.analysisSupport && first.analyzing) {
13693       SendToProgram("exit\n", &first);
13694       first.analyzing = FALSE;
13695     }
13696     thinkOutput[0] = NULLCHAR;
13697 }
13698
13699 void
13700 EditPositionDone(Boolean fakeRights)
13701 {
13702     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13703
13704     startedFromSetupPosition = TRUE;
13705     InitChessProgram(&first, FALSE);
13706     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13707       boards[0][EP_STATUS] = EP_NONE;
13708       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13709     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13710         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13711         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13712       } else boards[0][CASTLING][2] = NoRights;
13713     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13714         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13715         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13716       } else boards[0][CASTLING][5] = NoRights;
13717     }
13718     SendToProgram("force\n", &first);
13719     if (blackPlaysFirst) {
13720         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13721         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13722         currentMove = forwardMostMove = backwardMostMove = 1;
13723         CopyBoard(boards[1], boards[0]);
13724     } else {
13725         currentMove = forwardMostMove = backwardMostMove = 0;
13726     }
13727     SendBoard(&first, forwardMostMove);
13728     if (appData.debugMode) {
13729         fprintf(debugFP, "EditPosDone\n");
13730     }
13731     DisplayTitle("");
13732     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13733     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13734     gameMode = EditGame;
13735     ModeHighlight();
13736     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13737     ClearHighlights(); /* [AS] */
13738 }
13739
13740 /* Pause for `ms' milliseconds */
13741 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13742 void
13743 TimeDelay(ms)
13744      long ms;
13745 {
13746     TimeMark m1, m2;
13747
13748     GetTimeMark(&m1);
13749     do {
13750         GetTimeMark(&m2);
13751     } while (SubtractTimeMarks(&m2, &m1) < ms);
13752 }
13753
13754 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13755 void
13756 SendMultiLineToICS(buf)
13757      char *buf;
13758 {
13759     char temp[MSG_SIZ+1], *p;
13760     int len;
13761
13762     len = strlen(buf);
13763     if (len > MSG_SIZ)
13764       len = MSG_SIZ;
13765
13766     strncpy(temp, buf, len);
13767     temp[len] = 0;
13768
13769     p = temp;
13770     while (*p) {
13771         if (*p == '\n' || *p == '\r')
13772           *p = ' ';
13773         ++p;
13774     }
13775
13776     strcat(temp, "\n");
13777     SendToICS(temp);
13778     SendToPlayer(temp, strlen(temp));
13779 }
13780
13781 void
13782 SetWhiteToPlayEvent()
13783 {
13784     if (gameMode == EditPosition) {
13785         blackPlaysFirst = FALSE;
13786         DisplayBothClocks();    /* works because currentMove is 0 */
13787     } else if (gameMode == IcsExamining) {
13788         SendToICS(ics_prefix);
13789         SendToICS("tomove white\n");
13790     }
13791 }
13792
13793 void
13794 SetBlackToPlayEvent()
13795 {
13796     if (gameMode == EditPosition) {
13797         blackPlaysFirst = TRUE;
13798         currentMove = 1;        /* kludge */
13799         DisplayBothClocks();
13800         currentMove = 0;
13801     } else if (gameMode == IcsExamining) {
13802         SendToICS(ics_prefix);
13803         SendToICS("tomove black\n");
13804     }
13805 }
13806
13807 void
13808 EditPositionMenuEvent(selection, x, y)
13809      ChessSquare selection;
13810      int x, y;
13811 {
13812     char buf[MSG_SIZ];
13813     ChessSquare piece = boards[0][y][x];
13814
13815     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13816
13817     switch (selection) {
13818       case ClearBoard:
13819         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13820             SendToICS(ics_prefix);
13821             SendToICS("bsetup clear\n");
13822         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13823             SendToICS(ics_prefix);
13824             SendToICS("clearboard\n");
13825         } else {
13826             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13827                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13828                 for (y = 0; y < BOARD_HEIGHT; y++) {
13829                     if (gameMode == IcsExamining) {
13830                         if (boards[currentMove][y][x] != EmptySquare) {
13831                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13832                                     AAA + x, ONE + y);
13833                             SendToICS(buf);
13834                         }
13835                     } else {
13836                         boards[0][y][x] = p;
13837                     }
13838                 }
13839             }
13840         }
13841         if (gameMode == EditPosition) {
13842             DrawPosition(FALSE, boards[0]);
13843         }
13844         break;
13845
13846       case WhitePlay:
13847         SetWhiteToPlayEvent();
13848         break;
13849
13850       case BlackPlay:
13851         SetBlackToPlayEvent();
13852         break;
13853
13854       case EmptySquare:
13855         if (gameMode == IcsExamining) {
13856             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13857             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13858             SendToICS(buf);
13859         } else {
13860             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13861                 if(x == BOARD_LEFT-2) {
13862                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13863                     boards[0][y][1] = 0;
13864                 } else
13865                 if(x == BOARD_RGHT+1) {
13866                     if(y >= gameInfo.holdingsSize) break;
13867                     boards[0][y][BOARD_WIDTH-2] = 0;
13868                 } else break;
13869             }
13870             boards[0][y][x] = EmptySquare;
13871             DrawPosition(FALSE, boards[0]);
13872         }
13873         break;
13874
13875       case PromotePiece:
13876         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13877            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13878             selection = (ChessSquare) (PROMOTED piece);
13879         } else if(piece == EmptySquare) selection = WhiteSilver;
13880         else selection = (ChessSquare)((int)piece - 1);
13881         goto defaultlabel;
13882
13883       case DemotePiece:
13884         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13885            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13886             selection = (ChessSquare) (DEMOTED piece);
13887         } else if(piece == EmptySquare) selection = BlackSilver;
13888         else selection = (ChessSquare)((int)piece + 1);
13889         goto defaultlabel;
13890
13891       case WhiteQueen:
13892       case BlackQueen:
13893         if(gameInfo.variant == VariantShatranj ||
13894            gameInfo.variant == VariantXiangqi  ||
13895            gameInfo.variant == VariantCourier  ||
13896            gameInfo.variant == VariantMakruk     )
13897             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13898         goto defaultlabel;
13899
13900       case WhiteKing:
13901       case BlackKing:
13902         if(gameInfo.variant == VariantXiangqi)
13903             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13904         if(gameInfo.variant == VariantKnightmate)
13905             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13906       default:
13907         defaultlabel:
13908         if (gameMode == IcsExamining) {
13909             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13910             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13911                      PieceToChar(selection), AAA + x, ONE + y);
13912             SendToICS(buf);
13913         } else {
13914             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13915                 int n;
13916                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13917                     n = PieceToNumber(selection - BlackPawn);
13918                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13919                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13920                     boards[0][BOARD_HEIGHT-1-n][1]++;
13921                 } else
13922                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13923                     n = PieceToNumber(selection);
13924                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13925                     boards[0][n][BOARD_WIDTH-1] = selection;
13926                     boards[0][n][BOARD_WIDTH-2]++;
13927                 }
13928             } else
13929             boards[0][y][x] = selection;
13930             DrawPosition(TRUE, boards[0]);
13931         }
13932         break;
13933     }
13934 }
13935
13936
13937 void
13938 DropMenuEvent(selection, x, y)
13939      ChessSquare selection;
13940      int x, y;
13941 {
13942     ChessMove moveType;
13943
13944     switch (gameMode) {
13945       case IcsPlayingWhite:
13946       case MachinePlaysBlack:
13947         if (!WhiteOnMove(currentMove)) {
13948             DisplayMoveError(_("It is Black's turn"));
13949             return;
13950         }
13951         moveType = WhiteDrop;
13952         break;
13953       case IcsPlayingBlack:
13954       case MachinePlaysWhite:
13955         if (WhiteOnMove(currentMove)) {
13956             DisplayMoveError(_("It is White's turn"));
13957             return;
13958         }
13959         moveType = BlackDrop;
13960         break;
13961       case EditGame:
13962         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13963         break;
13964       default:
13965         return;
13966     }
13967
13968     if (moveType == BlackDrop && selection < BlackPawn) {
13969       selection = (ChessSquare) ((int) selection
13970                                  + (int) BlackPawn - (int) WhitePawn);
13971     }
13972     if (boards[currentMove][y][x] != EmptySquare) {
13973         DisplayMoveError(_("That square is occupied"));
13974         return;
13975     }
13976
13977     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13978 }
13979
13980 void
13981 AcceptEvent()
13982 {
13983     /* Accept a pending offer of any kind from opponent */
13984
13985     if (appData.icsActive) {
13986         SendToICS(ics_prefix);
13987         SendToICS("accept\n");
13988     } else if (cmailMsgLoaded) {
13989         if (currentMove == cmailOldMove &&
13990             commentList[cmailOldMove] != NULL &&
13991             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13992                    "Black offers a draw" : "White offers a draw")) {
13993             TruncateGame();
13994             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13995             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13996         } else {
13997             DisplayError(_("There is no pending offer on this move"), 0);
13998             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13999         }
14000     } else {
14001         /* Not used for offers from chess program */
14002     }
14003 }
14004
14005 void
14006 DeclineEvent()
14007 {
14008     /* Decline a pending offer of any kind from opponent */
14009
14010     if (appData.icsActive) {
14011         SendToICS(ics_prefix);
14012         SendToICS("decline\n");
14013     } else if (cmailMsgLoaded) {
14014         if (currentMove == cmailOldMove &&
14015             commentList[cmailOldMove] != NULL &&
14016             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14017                    "Black offers a draw" : "White offers a draw")) {
14018 #ifdef NOTDEF
14019             AppendComment(cmailOldMove, "Draw declined", TRUE);
14020             DisplayComment(cmailOldMove - 1, "Draw declined");
14021 #endif /*NOTDEF*/
14022         } else {
14023             DisplayError(_("There is no pending offer on this move"), 0);
14024         }
14025     } else {
14026         /* Not used for offers from chess program */
14027     }
14028 }
14029
14030 void
14031 RematchEvent()
14032 {
14033     /* Issue ICS rematch command */
14034     if (appData.icsActive) {
14035         SendToICS(ics_prefix);
14036         SendToICS("rematch\n");
14037     }
14038 }
14039
14040 void
14041 CallFlagEvent()
14042 {
14043     /* Call your opponent's flag (claim a win on time) */
14044     if (appData.icsActive) {
14045         SendToICS(ics_prefix);
14046         SendToICS("flag\n");
14047     } else {
14048         switch (gameMode) {
14049           default:
14050             return;
14051           case MachinePlaysWhite:
14052             if (whiteFlag) {
14053                 if (blackFlag)
14054                   GameEnds(GameIsDrawn, "Both players ran out of time",
14055                            GE_PLAYER);
14056                 else
14057                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14058             } else {
14059                 DisplayError(_("Your opponent is not out of time"), 0);
14060             }
14061             break;
14062           case MachinePlaysBlack:
14063             if (blackFlag) {
14064                 if (whiteFlag)
14065                   GameEnds(GameIsDrawn, "Both players ran out of time",
14066                            GE_PLAYER);
14067                 else
14068                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14069             } else {
14070                 DisplayError(_("Your opponent is not out of time"), 0);
14071             }
14072             break;
14073         }
14074     }
14075 }
14076
14077 void
14078 ClockClick(int which)
14079 {       // [HGM] code moved to back-end from winboard.c
14080         if(which) { // black clock
14081           if (gameMode == EditPosition || gameMode == IcsExamining) {
14082             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14083             SetBlackToPlayEvent();
14084           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14085           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14086           } else if (shiftKey) {
14087             AdjustClock(which, -1);
14088           } else if (gameMode == IcsPlayingWhite ||
14089                      gameMode == MachinePlaysBlack) {
14090             CallFlagEvent();
14091           }
14092         } else { // white clock
14093           if (gameMode == EditPosition || gameMode == IcsExamining) {
14094             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14095             SetWhiteToPlayEvent();
14096           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14097           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14098           } else if (shiftKey) {
14099             AdjustClock(which, -1);
14100           } else if (gameMode == IcsPlayingBlack ||
14101                    gameMode == MachinePlaysWhite) {
14102             CallFlagEvent();
14103           }
14104         }
14105 }
14106
14107 void
14108 DrawEvent()
14109 {
14110     /* Offer draw or accept pending draw offer from opponent */
14111
14112     if (appData.icsActive) {
14113         /* Note: tournament rules require draw offers to be
14114            made after you make your move but before you punch
14115            your clock.  Currently ICS doesn't let you do that;
14116            instead, you immediately punch your clock after making
14117            a move, but you can offer a draw at any time. */
14118
14119         SendToICS(ics_prefix);
14120         SendToICS("draw\n");
14121         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14122     } else if (cmailMsgLoaded) {
14123         if (currentMove == cmailOldMove &&
14124             commentList[cmailOldMove] != NULL &&
14125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14126                    "Black offers a draw" : "White offers a draw")) {
14127             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14129         } else if (currentMove == cmailOldMove + 1) {
14130             char *offer = WhiteOnMove(cmailOldMove) ?
14131               "White offers a draw" : "Black offers a draw";
14132             AppendComment(currentMove, offer, TRUE);
14133             DisplayComment(currentMove - 1, offer);
14134             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14135         } else {
14136             DisplayError(_("You must make your move before offering a draw"), 0);
14137             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14138         }
14139     } else if (first.offeredDraw) {
14140         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14141     } else {
14142         if (first.sendDrawOffers) {
14143             SendToProgram("draw\n", &first);
14144             userOfferedDraw = TRUE;
14145         }
14146     }
14147 }
14148
14149 void
14150 AdjournEvent()
14151 {
14152     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14153
14154     if (appData.icsActive) {
14155         SendToICS(ics_prefix);
14156         SendToICS("adjourn\n");
14157     } else {
14158         /* Currently GNU Chess doesn't offer or accept Adjourns */
14159     }
14160 }
14161
14162
14163 void
14164 AbortEvent()
14165 {
14166     /* Offer Abort or accept pending Abort offer from opponent */
14167
14168     if (appData.icsActive) {
14169         SendToICS(ics_prefix);
14170         SendToICS("abort\n");
14171     } else {
14172         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14173     }
14174 }
14175
14176 void
14177 ResignEvent()
14178 {
14179     /* Resign.  You can do this even if it's not your turn. */
14180
14181     if (appData.icsActive) {
14182         SendToICS(ics_prefix);
14183         SendToICS("resign\n");
14184     } else {
14185         switch (gameMode) {
14186           case MachinePlaysWhite:
14187             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14188             break;
14189           case MachinePlaysBlack:
14190             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14191             break;
14192           case EditGame:
14193             if (cmailMsgLoaded) {
14194                 TruncateGame();
14195                 if (WhiteOnMove(cmailOldMove)) {
14196                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14197                 } else {
14198                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14199                 }
14200                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14201             }
14202             break;
14203           default:
14204             break;
14205         }
14206     }
14207 }
14208
14209
14210 void
14211 StopObservingEvent()
14212 {
14213     /* Stop observing current games */
14214     SendToICS(ics_prefix);
14215     SendToICS("unobserve\n");
14216 }
14217
14218 void
14219 StopExaminingEvent()
14220 {
14221     /* Stop observing current game */
14222     SendToICS(ics_prefix);
14223     SendToICS("unexamine\n");
14224 }
14225
14226 void
14227 ForwardInner(target)
14228      int target;
14229 {
14230     int limit;
14231
14232     if (appData.debugMode)
14233         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14234                 target, currentMove, forwardMostMove);
14235
14236     if (gameMode == EditPosition)
14237       return;
14238
14239     MarkTargetSquares(1);
14240
14241     if (gameMode == PlayFromGameFile && !pausing)
14242       PauseEvent();
14243
14244     if (gameMode == IcsExamining && pausing)
14245       limit = pauseExamForwardMostMove;
14246     else
14247       limit = forwardMostMove;
14248
14249     if (target > limit) target = limit;
14250
14251     if (target > 0 && moveList[target - 1][0]) {
14252         int fromX, fromY, toX, toY;
14253         toX = moveList[target - 1][2] - AAA;
14254         toY = moveList[target - 1][3] - ONE;
14255         if (moveList[target - 1][1] == '@') {
14256             if (appData.highlightLastMove) {
14257                 SetHighlights(-1, -1, toX, toY);
14258             }
14259         } else {
14260             fromX = moveList[target - 1][0] - AAA;
14261             fromY = moveList[target - 1][1] - ONE;
14262             if (target == currentMove + 1) {
14263                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14264             }
14265             if (appData.highlightLastMove) {
14266                 SetHighlights(fromX, fromY, toX, toY);
14267             }
14268         }
14269     }
14270     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14271         gameMode == Training || gameMode == PlayFromGameFile ||
14272         gameMode == AnalyzeFile) {
14273         while (currentMove < target) {
14274             SendMoveToProgram(currentMove++, &first);
14275         }
14276     } else {
14277         currentMove = target;
14278     }
14279
14280     if (gameMode == EditGame || gameMode == EndOfGame) {
14281         whiteTimeRemaining = timeRemaining[0][currentMove];
14282         blackTimeRemaining = timeRemaining[1][currentMove];
14283     }
14284     DisplayBothClocks();
14285     DisplayMove(currentMove - 1);
14286     DrawPosition(FALSE, boards[currentMove]);
14287     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14288     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14289         DisplayComment(currentMove - 1, commentList[currentMove]);
14290     }
14291 }
14292
14293
14294 void
14295 ForwardEvent()
14296 {
14297     if (gameMode == IcsExamining && !pausing) {
14298         SendToICS(ics_prefix);
14299         SendToICS("forward\n");
14300     } else {
14301         ForwardInner(currentMove + 1);
14302     }
14303 }
14304
14305 void
14306 ToEndEvent()
14307 {
14308     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14309         /* to optimze, we temporarily turn off analysis mode while we feed
14310          * the remaining moves to the engine. Otherwise we get analysis output
14311          * after each move.
14312          */
14313         if (first.analysisSupport) {
14314           SendToProgram("exit\nforce\n", &first);
14315           first.analyzing = FALSE;
14316         }
14317     }
14318
14319     if (gameMode == IcsExamining && !pausing) {
14320         SendToICS(ics_prefix);
14321         SendToICS("forward 999999\n");
14322     } else {
14323         ForwardInner(forwardMostMove);
14324     }
14325
14326     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14327         /* we have fed all the moves, so reactivate analysis mode */
14328         SendToProgram("analyze\n", &first);
14329         first.analyzing = TRUE;
14330         /*first.maybeThinking = TRUE;*/
14331         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14332     }
14333 }
14334
14335 void
14336 BackwardInner(target)
14337      int target;
14338 {
14339     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14340
14341     if (appData.debugMode)
14342         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14343                 target, currentMove, forwardMostMove);
14344
14345     if (gameMode == EditPosition) return;
14346     MarkTargetSquares(1);
14347     if (currentMove <= backwardMostMove) {
14348         ClearHighlights();
14349         DrawPosition(full_redraw, boards[currentMove]);
14350         return;
14351     }
14352     if (gameMode == PlayFromGameFile && !pausing)
14353       PauseEvent();
14354
14355     if (moveList[target][0]) {
14356         int fromX, fromY, toX, toY;
14357         toX = moveList[target][2] - AAA;
14358         toY = moveList[target][3] - ONE;
14359         if (moveList[target][1] == '@') {
14360             if (appData.highlightLastMove) {
14361                 SetHighlights(-1, -1, toX, toY);
14362             }
14363         } else {
14364             fromX = moveList[target][0] - AAA;
14365             fromY = moveList[target][1] - ONE;
14366             if (target == currentMove - 1) {
14367                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14368             }
14369             if (appData.highlightLastMove) {
14370                 SetHighlights(fromX, fromY, toX, toY);
14371             }
14372         }
14373     }
14374     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14375         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14376         while (currentMove > target) {
14377             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14378                 // null move cannot be undone. Reload program with move history before it.
14379                 int i;
14380                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14381                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14382                 }
14383                 SendBoard(&first, i); 
14384                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14385                 break;
14386             }
14387             SendToProgram("undo\n", &first);
14388             currentMove--;
14389         }
14390     } else {
14391         currentMove = target;
14392     }
14393
14394     if (gameMode == EditGame || gameMode == EndOfGame) {
14395         whiteTimeRemaining = timeRemaining[0][currentMove];
14396         blackTimeRemaining = timeRemaining[1][currentMove];
14397     }
14398     DisplayBothClocks();
14399     DisplayMove(currentMove - 1);
14400     DrawPosition(full_redraw, boards[currentMove]);
14401     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14402     // [HGM] PV info: routine tests if comment empty
14403     DisplayComment(currentMove - 1, commentList[currentMove]);
14404 }
14405
14406 void
14407 BackwardEvent()
14408 {
14409     if (gameMode == IcsExamining && !pausing) {
14410         SendToICS(ics_prefix);
14411         SendToICS("backward\n");
14412     } else {
14413         BackwardInner(currentMove - 1);
14414     }
14415 }
14416
14417 void
14418 ToStartEvent()
14419 {
14420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14421         /* to optimize, we temporarily turn off analysis mode while we undo
14422          * all the moves. Otherwise we get analysis output after each undo.
14423          */
14424         if (first.analysisSupport) {
14425           SendToProgram("exit\nforce\n", &first);
14426           first.analyzing = FALSE;
14427         }
14428     }
14429
14430     if (gameMode == IcsExamining && !pausing) {
14431         SendToICS(ics_prefix);
14432         SendToICS("backward 999999\n");
14433     } else {
14434         BackwardInner(backwardMostMove);
14435     }
14436
14437     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14438         /* we have fed all the moves, so reactivate analysis mode */
14439         SendToProgram("analyze\n", &first);
14440         first.analyzing = TRUE;
14441         /*first.maybeThinking = TRUE;*/
14442         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14443     }
14444 }
14445
14446 void
14447 ToNrEvent(int to)
14448 {
14449   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14450   if (to >= forwardMostMove) to = forwardMostMove;
14451   if (to <= backwardMostMove) to = backwardMostMove;
14452   if (to < currentMove) {
14453     BackwardInner(to);
14454   } else {
14455     ForwardInner(to);
14456   }
14457 }
14458
14459 void
14460 RevertEvent(Boolean annotate)
14461 {
14462     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14463         return;
14464     }
14465     if (gameMode != IcsExamining) {
14466         DisplayError(_("You are not examining a game"), 0);
14467         return;
14468     }
14469     if (pausing) {
14470         DisplayError(_("You can't revert while pausing"), 0);
14471         return;
14472     }
14473     SendToICS(ics_prefix);
14474     SendToICS("revert\n");
14475 }
14476
14477 void
14478 RetractMoveEvent()
14479 {
14480     switch (gameMode) {
14481       case MachinePlaysWhite:
14482       case MachinePlaysBlack:
14483         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14484             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14485             return;
14486         }
14487         if (forwardMostMove < 2) return;
14488         currentMove = forwardMostMove = forwardMostMove - 2;
14489         whiteTimeRemaining = timeRemaining[0][currentMove];
14490         blackTimeRemaining = timeRemaining[1][currentMove];
14491         DisplayBothClocks();
14492         DisplayMove(currentMove - 1);
14493         ClearHighlights();/*!! could figure this out*/
14494         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14495         SendToProgram("remove\n", &first);
14496         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14497         break;
14498
14499       case BeginningOfGame:
14500       default:
14501         break;
14502
14503       case IcsPlayingWhite:
14504       case IcsPlayingBlack:
14505         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14506             SendToICS(ics_prefix);
14507             SendToICS("takeback 2\n");
14508         } else {
14509             SendToICS(ics_prefix);
14510             SendToICS("takeback 1\n");
14511         }
14512         break;
14513     }
14514 }
14515
14516 void
14517 MoveNowEvent()
14518 {
14519     ChessProgramState *cps;
14520
14521     switch (gameMode) {
14522       case MachinePlaysWhite:
14523         if (!WhiteOnMove(forwardMostMove)) {
14524             DisplayError(_("It is your turn"), 0);
14525             return;
14526         }
14527         cps = &first;
14528         break;
14529       case MachinePlaysBlack:
14530         if (WhiteOnMove(forwardMostMove)) {
14531             DisplayError(_("It is your turn"), 0);
14532             return;
14533         }
14534         cps = &first;
14535         break;
14536       case TwoMachinesPlay:
14537         if (WhiteOnMove(forwardMostMove) ==
14538             (first.twoMachinesColor[0] == 'w')) {
14539             cps = &first;
14540         } else {
14541             cps = &second;
14542         }
14543         break;
14544       case BeginningOfGame:
14545       default:
14546         return;
14547     }
14548     SendToProgram("?\n", cps);
14549 }
14550
14551 void
14552 TruncateGameEvent()
14553 {
14554     EditGameEvent();
14555     if (gameMode != EditGame) return;
14556     TruncateGame();
14557 }
14558
14559 void
14560 TruncateGame()
14561 {
14562     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14563     if (forwardMostMove > currentMove) {
14564         if (gameInfo.resultDetails != NULL) {
14565             free(gameInfo.resultDetails);
14566             gameInfo.resultDetails = NULL;
14567             gameInfo.result = GameUnfinished;
14568         }
14569         forwardMostMove = currentMove;
14570         HistorySet(parseList, backwardMostMove, forwardMostMove,
14571                    currentMove-1);
14572     }
14573 }
14574
14575 void
14576 HintEvent()
14577 {
14578     if (appData.noChessProgram) return;
14579     switch (gameMode) {
14580       case MachinePlaysWhite:
14581         if (WhiteOnMove(forwardMostMove)) {
14582             DisplayError(_("Wait until your turn"), 0);
14583             return;
14584         }
14585         break;
14586       case BeginningOfGame:
14587       case MachinePlaysBlack:
14588         if (!WhiteOnMove(forwardMostMove)) {
14589             DisplayError(_("Wait until your turn"), 0);
14590             return;
14591         }
14592         break;
14593       default:
14594         DisplayError(_("No hint available"), 0);
14595         return;
14596     }
14597     SendToProgram("hint\n", &first);
14598     hintRequested = TRUE;
14599 }
14600
14601 void
14602 BookEvent()
14603 {
14604     if (appData.noChessProgram) return;
14605     switch (gameMode) {
14606       case MachinePlaysWhite:
14607         if (WhiteOnMove(forwardMostMove)) {
14608             DisplayError(_("Wait until your turn"), 0);
14609             return;
14610         }
14611         break;
14612       case BeginningOfGame:
14613       case MachinePlaysBlack:
14614         if (!WhiteOnMove(forwardMostMove)) {
14615             DisplayError(_("Wait until your turn"), 0);
14616             return;
14617         }
14618         break;
14619       case EditPosition:
14620         EditPositionDone(TRUE);
14621         break;
14622       case TwoMachinesPlay:
14623         return;
14624       default:
14625         break;
14626     }
14627     SendToProgram("bk\n", &first);
14628     bookOutput[0] = NULLCHAR;
14629     bookRequested = TRUE;
14630 }
14631
14632 void
14633 AboutGameEvent()
14634 {
14635     char *tags = PGNTags(&gameInfo);
14636     TagsPopUp(tags, CmailMsg());
14637     free(tags);
14638 }
14639
14640 /* end button procedures */
14641
14642 void
14643 PrintPosition(fp, move)
14644      FILE *fp;
14645      int move;
14646 {
14647     int i, j;
14648
14649     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14650         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14651             char c = PieceToChar(boards[move][i][j]);
14652             fputc(c == 'x' ? '.' : c, fp);
14653             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14654         }
14655     }
14656     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14657       fprintf(fp, "white to play\n");
14658     else
14659       fprintf(fp, "black to play\n");
14660 }
14661
14662 void
14663 PrintOpponents(fp)
14664      FILE *fp;
14665 {
14666     if (gameInfo.white != NULL) {
14667         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14668     } else {
14669         fprintf(fp, "\n");
14670     }
14671 }
14672
14673 /* Find last component of program's own name, using some heuristics */
14674 void
14675 TidyProgramName(prog, host, buf)
14676      char *prog, *host, buf[MSG_SIZ];
14677 {
14678     char *p, *q;
14679     int local = (strcmp(host, "localhost") == 0);
14680     while (!local && (p = strchr(prog, ';')) != NULL) {
14681         p++;
14682         while (*p == ' ') p++;
14683         prog = p;
14684     }
14685     if (*prog == '"' || *prog == '\'') {
14686         q = strchr(prog + 1, *prog);
14687     } else {
14688         q = strchr(prog, ' ');
14689     }
14690     if (q == NULL) q = prog + strlen(prog);
14691     p = q;
14692     while (p >= prog && *p != '/' && *p != '\\') p--;
14693     p++;
14694     if(p == prog && *p == '"') p++;
14695     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14696     memcpy(buf, p, q - p);
14697     buf[q - p] = NULLCHAR;
14698     if (!local) {
14699         strcat(buf, "@");
14700         strcat(buf, host);
14701     }
14702 }
14703
14704 char *
14705 TimeControlTagValue()
14706 {
14707     char buf[MSG_SIZ];
14708     if (!appData.clockMode) {
14709       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14710     } else if (movesPerSession > 0) {
14711       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14712     } else if (timeIncrement == 0) {
14713       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14714     } else {
14715       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14716     }
14717     return StrSave(buf);
14718 }
14719
14720 void
14721 SetGameInfo()
14722 {
14723     /* This routine is used only for certain modes */
14724     VariantClass v = gameInfo.variant;
14725     ChessMove r = GameUnfinished;
14726     char *p = NULL;
14727
14728     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14729         r = gameInfo.result;
14730         p = gameInfo.resultDetails;
14731         gameInfo.resultDetails = NULL;
14732     }
14733     ClearGameInfo(&gameInfo);
14734     gameInfo.variant = v;
14735
14736     switch (gameMode) {
14737       case MachinePlaysWhite:
14738         gameInfo.event = StrSave( appData.pgnEventHeader );
14739         gameInfo.site = StrSave(HostName());
14740         gameInfo.date = PGNDate();
14741         gameInfo.round = StrSave("-");
14742         gameInfo.white = StrSave(first.tidy);
14743         gameInfo.black = StrSave(UserName());
14744         gameInfo.timeControl = TimeControlTagValue();
14745         break;
14746
14747       case MachinePlaysBlack:
14748         gameInfo.event = StrSave( appData.pgnEventHeader );
14749         gameInfo.site = StrSave(HostName());
14750         gameInfo.date = PGNDate();
14751         gameInfo.round = StrSave("-");
14752         gameInfo.white = StrSave(UserName());
14753         gameInfo.black = StrSave(first.tidy);
14754         gameInfo.timeControl = TimeControlTagValue();
14755         break;
14756
14757       case TwoMachinesPlay:
14758         gameInfo.event = StrSave( appData.pgnEventHeader );
14759         gameInfo.site = StrSave(HostName());
14760         gameInfo.date = PGNDate();
14761         if (roundNr > 0) {
14762             char buf[MSG_SIZ];
14763             snprintf(buf, MSG_SIZ, "%d", roundNr);
14764             gameInfo.round = StrSave(buf);
14765         } else {
14766             gameInfo.round = StrSave("-");
14767         }
14768         if (first.twoMachinesColor[0] == 'w') {
14769             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14770             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14771         } else {
14772             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14773             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14774         }
14775         gameInfo.timeControl = TimeControlTagValue();
14776         break;
14777
14778       case EditGame:
14779         gameInfo.event = StrSave("Edited game");
14780         gameInfo.site = StrSave(HostName());
14781         gameInfo.date = PGNDate();
14782         gameInfo.round = StrSave("-");
14783         gameInfo.white = StrSave("-");
14784         gameInfo.black = StrSave("-");
14785         gameInfo.result = r;
14786         gameInfo.resultDetails = p;
14787         break;
14788
14789       case EditPosition:
14790         gameInfo.event = StrSave("Edited position");
14791         gameInfo.site = StrSave(HostName());
14792         gameInfo.date = PGNDate();
14793         gameInfo.round = StrSave("-");
14794         gameInfo.white = StrSave("-");
14795         gameInfo.black = StrSave("-");
14796         break;
14797
14798       case IcsPlayingWhite:
14799       case IcsPlayingBlack:
14800       case IcsObserving:
14801       case IcsExamining:
14802         break;
14803
14804       case PlayFromGameFile:
14805         gameInfo.event = StrSave("Game from non-PGN file");
14806         gameInfo.site = StrSave(HostName());
14807         gameInfo.date = PGNDate();
14808         gameInfo.round = StrSave("-");
14809         gameInfo.white = StrSave("?");
14810         gameInfo.black = StrSave("?");
14811         break;
14812
14813       default:
14814         break;
14815     }
14816 }
14817
14818 void
14819 ReplaceComment(index, text)
14820      int index;
14821      char *text;
14822 {
14823     int len;
14824     char *p;
14825     float score;
14826
14827     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14828        pvInfoList[index-1].depth == len &&
14829        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14830        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14831     while (*text == '\n') text++;
14832     len = strlen(text);
14833     while (len > 0 && text[len - 1] == '\n') len--;
14834
14835     if (commentList[index] != NULL)
14836       free(commentList[index]);
14837
14838     if (len == 0) {
14839         commentList[index] = NULL;
14840         return;
14841     }
14842   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14843       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14844       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14845     commentList[index] = (char *) malloc(len + 2);
14846     strncpy(commentList[index], text, len);
14847     commentList[index][len] = '\n';
14848     commentList[index][len + 1] = NULLCHAR;
14849   } else {
14850     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14851     char *p;
14852     commentList[index] = (char *) malloc(len + 7);
14853     safeStrCpy(commentList[index], "{\n", 3);
14854     safeStrCpy(commentList[index]+2, text, len+1);
14855     commentList[index][len+2] = NULLCHAR;
14856     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14857     strcat(commentList[index], "\n}\n");
14858   }
14859 }
14860
14861 void
14862 CrushCRs(text)
14863      char *text;
14864 {
14865   char *p = text;
14866   char *q = text;
14867   char ch;
14868
14869   do {
14870     ch = *p++;
14871     if (ch == '\r') continue;
14872     *q++ = ch;
14873   } while (ch != '\0');
14874 }
14875
14876 void
14877 AppendComment(index, text, addBraces)
14878      int index;
14879      char *text;
14880      Boolean addBraces; // [HGM] braces: tells if we should add {}
14881 {
14882     int oldlen, len;
14883     char *old;
14884
14885 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14886     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14887
14888     CrushCRs(text);
14889     while (*text == '\n') text++;
14890     len = strlen(text);
14891     while (len > 0 && text[len - 1] == '\n') len--;
14892
14893     if (len == 0) return;
14894
14895     if (commentList[index] != NULL) {
14896       Boolean addClosingBrace = addBraces;
14897         old = commentList[index];
14898         oldlen = strlen(old);
14899         while(commentList[index][oldlen-1] ==  '\n')
14900           commentList[index][--oldlen] = NULLCHAR;
14901         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14902         safeStrCpy(commentList[index], old, oldlen + len + 6);
14903         free(old);
14904         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14905         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14906           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14907           while (*text == '\n') { text++; len--; }
14908           commentList[index][--oldlen] = NULLCHAR;
14909       }
14910         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14911         else          strcat(commentList[index], "\n");
14912         strcat(commentList[index], text);
14913         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14914         else          strcat(commentList[index], "\n");
14915     } else {
14916         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14917         if(addBraces)
14918           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14919         else commentList[index][0] = NULLCHAR;
14920         strcat(commentList[index], text);
14921         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14922         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14923     }
14924 }
14925
14926 static char * FindStr( char * text, char * sub_text )
14927 {
14928     char * result = strstr( text, sub_text );
14929
14930     if( result != NULL ) {
14931         result += strlen( sub_text );
14932     }
14933
14934     return result;
14935 }
14936
14937 /* [AS] Try to extract PV info from PGN comment */
14938 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14939 char *GetInfoFromComment( int index, char * text )
14940 {
14941     char * sep = text, *p;
14942
14943     if( text != NULL && index > 0 ) {
14944         int score = 0;
14945         int depth = 0;
14946         int time = -1, sec = 0, deci;
14947         char * s_eval = FindStr( text, "[%eval " );
14948         char * s_emt = FindStr( text, "[%emt " );
14949
14950         if( s_eval != NULL || s_emt != NULL ) {
14951             /* New style */
14952             char delim;
14953
14954             if( s_eval != NULL ) {
14955                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14956                     return text;
14957                 }
14958
14959                 if( delim != ']' ) {
14960                     return text;
14961                 }
14962             }
14963
14964             if( s_emt != NULL ) {
14965             }
14966                 return text;
14967         }
14968         else {
14969             /* We expect something like: [+|-]nnn.nn/dd */
14970             int score_lo = 0;
14971
14972             if(*text != '{') return text; // [HGM] braces: must be normal comment
14973
14974             sep = strchr( text, '/' );
14975             if( sep == NULL || sep < (text+4) ) {
14976                 return text;
14977             }
14978
14979             p = text;
14980             if(p[1] == '(') { // comment starts with PV
14981                p = strchr(p, ')'); // locate end of PV
14982                if(p == NULL || sep < p+5) return text;
14983                // at this point we have something like "{(.*) +0.23/6 ..."
14984                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14985                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14986                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14987             }
14988             time = -1; sec = -1; deci = -1;
14989             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14990                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14991                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14992                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14993                 return text;
14994             }
14995
14996             if( score_lo < 0 || score_lo >= 100 ) {
14997                 return text;
14998             }
14999
15000             if(sec >= 0) time = 600*time + 10*sec; else
15001             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15002
15003             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15004
15005             /* [HGM] PV time: now locate end of PV info */
15006             while( *++sep >= '0' && *sep <= '9'); // strip depth
15007             if(time >= 0)
15008             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15009             if(sec >= 0)
15010             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15011             if(deci >= 0)
15012             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15013             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15014         }
15015
15016         if( depth <= 0 ) {
15017             return text;
15018         }
15019
15020         if( time < 0 ) {
15021             time = -1;
15022         }
15023
15024         pvInfoList[index-1].depth = depth;
15025         pvInfoList[index-1].score = score;
15026         pvInfoList[index-1].time  = 10*time; // centi-sec
15027         if(*sep == '}') *sep = 0; else *--sep = '{';
15028         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15029     }
15030     return sep;
15031 }
15032
15033 void
15034 SendToProgram(message, cps)
15035      char *message;
15036      ChessProgramState *cps;
15037 {
15038     int count, outCount, error;
15039     char buf[MSG_SIZ];
15040
15041     if (cps->pr == NoProc) return;
15042     Attention(cps);
15043
15044     if (appData.debugMode) {
15045         TimeMark now;
15046         GetTimeMark(&now);
15047         fprintf(debugFP, "%ld >%-6s: %s",
15048                 SubtractTimeMarks(&now, &programStartTime),
15049                 cps->which, message);
15050     }
15051
15052     count = strlen(message);
15053     outCount = OutputToProcess(cps->pr, message, count, &error);
15054     if (outCount < count && !exiting
15055                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15056       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15057       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15058         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15059             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15060                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15061                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15062                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15063             } else {
15064                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15065                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15066                 gameInfo.result = res;
15067             }
15068             gameInfo.resultDetails = StrSave(buf);
15069         }
15070         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15071         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15072     }
15073 }
15074
15075 void
15076 ReceiveFromProgram(isr, closure, message, count, error)
15077      InputSourceRef isr;
15078      VOIDSTAR closure;
15079      char *message;
15080      int count;
15081      int error;
15082 {
15083     char *end_str;
15084     char buf[MSG_SIZ];
15085     ChessProgramState *cps = (ChessProgramState *)closure;
15086
15087     if (isr != cps->isr) return; /* Killed intentionally */
15088     if (count <= 0) {
15089         if (count == 0) {
15090             RemoveInputSource(cps->isr);
15091             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15092             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15093                     _(cps->which), cps->program);
15094         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15095                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15096                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15097                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15098                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15099                 } else {
15100                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15101                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15102                     gameInfo.result = res;
15103                 }
15104                 gameInfo.resultDetails = StrSave(buf);
15105             }
15106             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15107             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15108         } else {
15109             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15110                     _(cps->which), cps->program);
15111             RemoveInputSource(cps->isr);
15112
15113             /* [AS] Program is misbehaving badly... kill it */
15114             if( count == -2 ) {
15115                 DestroyChildProcess( cps->pr, 9 );
15116                 cps->pr = NoProc;
15117             }
15118
15119             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15120         }
15121         return;
15122     }
15123
15124     if ((end_str = strchr(message, '\r')) != NULL)
15125       *end_str = NULLCHAR;
15126     if ((end_str = strchr(message, '\n')) != NULL)
15127       *end_str = NULLCHAR;
15128
15129     if (appData.debugMode) {
15130         TimeMark now; int print = 1;
15131         char *quote = ""; char c; int i;
15132
15133         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15134                 char start = message[0];
15135                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15136                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15137                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15138                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15139                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15140                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15141                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15142                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15143                    sscanf(message, "hint: %c", &c)!=1 && 
15144                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15145                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15146                     print = (appData.engineComments >= 2);
15147                 }
15148                 message[0] = start; // restore original message
15149         }
15150         if(print) {
15151                 GetTimeMark(&now);
15152                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15153                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15154                         quote,
15155                         message);
15156         }
15157     }
15158
15159     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15160     if (appData.icsEngineAnalyze) {
15161         if (strstr(message, "whisper") != NULL ||
15162              strstr(message, "kibitz") != NULL ||
15163             strstr(message, "tellics") != NULL) return;
15164     }
15165
15166     HandleMachineMove(message, cps);
15167 }
15168
15169
15170 void
15171 SendTimeControl(cps, mps, tc, inc, sd, st)
15172      ChessProgramState *cps;
15173      int mps, inc, sd, st;
15174      long tc;
15175 {
15176     char buf[MSG_SIZ];
15177     int seconds;
15178
15179     if( timeControl_2 > 0 ) {
15180         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15181             tc = timeControl_2;
15182         }
15183     }
15184     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15185     inc /= cps->timeOdds;
15186     st  /= cps->timeOdds;
15187
15188     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15189
15190     if (st > 0) {
15191       /* Set exact time per move, normally using st command */
15192       if (cps->stKludge) {
15193         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15194         seconds = st % 60;
15195         if (seconds == 0) {
15196           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15197         } else {
15198           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15199         }
15200       } else {
15201         snprintf(buf, MSG_SIZ, "st %d\n", st);
15202       }
15203     } else {
15204       /* Set conventional or incremental time control, using level command */
15205       if (seconds == 0) {
15206         /* Note old gnuchess bug -- minutes:seconds used to not work.
15207            Fixed in later versions, but still avoid :seconds
15208            when seconds is 0. */
15209         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15210       } else {
15211         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15212                  seconds, inc/1000.);
15213       }
15214     }
15215     SendToProgram(buf, cps);
15216
15217     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15218     /* Orthogonally, limit search to given depth */
15219     if (sd > 0) {
15220       if (cps->sdKludge) {
15221         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15222       } else {
15223         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15224       }
15225       SendToProgram(buf, cps);
15226     }
15227
15228     if(cps->nps >= 0) { /* [HGM] nps */
15229         if(cps->supportsNPS == FALSE)
15230           cps->nps = -1; // don't use if engine explicitly says not supported!
15231         else {
15232           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15233           SendToProgram(buf, cps);
15234         }
15235     }
15236 }
15237
15238 ChessProgramState *WhitePlayer()
15239 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15240 {
15241     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15242        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15243         return &second;
15244     return &first;
15245 }
15246
15247 void
15248 SendTimeRemaining(cps, machineWhite)
15249      ChessProgramState *cps;
15250      int /*boolean*/ machineWhite;
15251 {
15252     char message[MSG_SIZ];
15253     long time, otime;
15254
15255     /* Note: this routine must be called when the clocks are stopped
15256        or when they have *just* been set or switched; otherwise
15257        it will be off by the time since the current tick started.
15258     */
15259     if (machineWhite) {
15260         time = whiteTimeRemaining / 10;
15261         otime = blackTimeRemaining / 10;
15262     } else {
15263         time = blackTimeRemaining / 10;
15264         otime = whiteTimeRemaining / 10;
15265     }
15266     /* [HGM] translate opponent's time by time-odds factor */
15267     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15268     if (appData.debugMode) {
15269         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15270     }
15271
15272     if (time <= 0) time = 1;
15273     if (otime <= 0) otime = 1;
15274
15275     snprintf(message, MSG_SIZ, "time %ld\n", time);
15276     SendToProgram(message, cps);
15277
15278     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15279     SendToProgram(message, cps);
15280 }
15281
15282 int
15283 BoolFeature(p, name, loc, cps)
15284      char **p;
15285      char *name;
15286      int *loc;
15287      ChessProgramState *cps;
15288 {
15289   char buf[MSG_SIZ];
15290   int len = strlen(name);
15291   int val;
15292
15293   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15294     (*p) += len + 1;
15295     sscanf(*p, "%d", &val);
15296     *loc = (val != 0);
15297     while (**p && **p != ' ')
15298       (*p)++;
15299     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15300     SendToProgram(buf, cps);
15301     return TRUE;
15302   }
15303   return FALSE;
15304 }
15305
15306 int
15307 IntFeature(p, name, loc, cps)
15308      char **p;
15309      char *name;
15310      int *loc;
15311      ChessProgramState *cps;
15312 {
15313   char buf[MSG_SIZ];
15314   int len = strlen(name);
15315   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15316     (*p) += len + 1;
15317     sscanf(*p, "%d", loc);
15318     while (**p && **p != ' ') (*p)++;
15319     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15320     SendToProgram(buf, cps);
15321     return TRUE;
15322   }
15323   return FALSE;
15324 }
15325
15326 int
15327 StringFeature(p, name, loc, cps)
15328      char **p;
15329      char *name;
15330      char loc[];
15331      ChessProgramState *cps;
15332 {
15333   char buf[MSG_SIZ];
15334   int len = strlen(name);
15335   if (strncmp((*p), name, len) == 0
15336       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15337     (*p) += len + 2;
15338     sscanf(*p, "%[^\"]", loc);
15339     while (**p && **p != '\"') (*p)++;
15340     if (**p == '\"') (*p)++;
15341     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15342     SendToProgram(buf, cps);
15343     return TRUE;
15344   }
15345   return FALSE;
15346 }
15347
15348 int
15349 ParseOption(Option *opt, ChessProgramState *cps)
15350 // [HGM] options: process the string that defines an engine option, and determine
15351 // name, type, default value, and allowed value range
15352 {
15353         char *p, *q, buf[MSG_SIZ];
15354         int n, min = (-1)<<31, max = 1<<31, def;
15355
15356         if(p = strstr(opt->name, " -spin ")) {
15357             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15358             if(max < min) max = min; // enforce consistency
15359             if(def < min) def = min;
15360             if(def > max) def = max;
15361             opt->value = def;
15362             opt->min = min;
15363             opt->max = max;
15364             opt->type = Spin;
15365         } else if((p = strstr(opt->name, " -slider "))) {
15366             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15367             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15368             if(max < min) max = min; // enforce consistency
15369             if(def < min) def = min;
15370             if(def > max) def = max;
15371             opt->value = def;
15372             opt->min = min;
15373             opt->max = max;
15374             opt->type = Spin; // Slider;
15375         } else if((p = strstr(opt->name, " -string "))) {
15376             opt->textValue = p+9;
15377             opt->type = TextBox;
15378         } else if((p = strstr(opt->name, " -file "))) {
15379             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15380             opt->textValue = p+7;
15381             opt->type = FileName; // FileName;
15382         } else if((p = strstr(opt->name, " -path "))) {
15383             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15384             opt->textValue = p+7;
15385             opt->type = PathName; // PathName;
15386         } else if(p = strstr(opt->name, " -check ")) {
15387             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15388             opt->value = (def != 0);
15389             opt->type = CheckBox;
15390         } else if(p = strstr(opt->name, " -combo ")) {
15391             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15392             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15393             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15394             opt->value = n = 0;
15395             while(q = StrStr(q, " /// ")) {
15396                 n++; *q = 0;    // count choices, and null-terminate each of them
15397                 q += 5;
15398                 if(*q == '*') { // remember default, which is marked with * prefix
15399                     q++;
15400                     opt->value = n;
15401                 }
15402                 cps->comboList[cps->comboCnt++] = q;
15403             }
15404             cps->comboList[cps->comboCnt++] = NULL;
15405             opt->max = n + 1;
15406             opt->type = ComboBox;
15407         } else if(p = strstr(opt->name, " -button")) {
15408             opt->type = Button;
15409         } else if(p = strstr(opt->name, " -save")) {
15410             opt->type = SaveButton;
15411         } else return FALSE;
15412         *p = 0; // terminate option name
15413         // now look if the command-line options define a setting for this engine option.
15414         if(cps->optionSettings && cps->optionSettings[0])
15415             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15416         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15417           snprintf(buf, MSG_SIZ, "option %s", p);
15418                 if(p = strstr(buf, ",")) *p = 0;
15419                 if(q = strchr(buf, '=')) switch(opt->type) {
15420                     case ComboBox:
15421                         for(n=0; n<opt->max; n++)
15422                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15423                         break;
15424                     case TextBox:
15425                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15426                         break;
15427                     case Spin:
15428                     case CheckBox:
15429                         opt->value = atoi(q+1);
15430                     default:
15431                         break;
15432                 }
15433                 strcat(buf, "\n");
15434                 SendToProgram(buf, cps);
15435         }
15436         return TRUE;
15437 }
15438
15439 void
15440 FeatureDone(cps, val)
15441      ChessProgramState* cps;
15442      int val;
15443 {
15444   DelayedEventCallback cb = GetDelayedEvent();
15445   if ((cb == InitBackEnd3 && cps == &first) ||
15446       (cb == SettingsMenuIfReady && cps == &second) ||
15447       (cb == LoadEngine) ||
15448       (cb == TwoMachinesEventIfReady)) {
15449     CancelDelayedEvent();
15450     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15451   }
15452   cps->initDone = val;
15453 }
15454
15455 /* Parse feature command from engine */
15456 void
15457 ParseFeatures(args, cps)
15458      char* args;
15459      ChessProgramState *cps;
15460 {
15461   char *p = args;
15462   char *q;
15463   int val;
15464   char buf[MSG_SIZ];
15465
15466   for (;;) {
15467     while (*p == ' ') p++;
15468     if (*p == NULLCHAR) return;
15469
15470     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15471     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15472     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15473     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15474     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15475     if (BoolFeature(&p, "reuse", &val, cps)) {
15476       /* Engine can disable reuse, but can't enable it if user said no */
15477       if (!val) cps->reuse = FALSE;
15478       continue;
15479     }
15480     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15481     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15482       if (gameMode == TwoMachinesPlay) {
15483         DisplayTwoMachinesTitle();
15484       } else {
15485         DisplayTitle("");
15486       }
15487       continue;
15488     }
15489     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15490     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15491     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15492     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15493     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15494     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15495     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15496     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15497     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15498     if (IntFeature(&p, "done", &val, cps)) {
15499       FeatureDone(cps, val);
15500       continue;
15501     }
15502     /* Added by Tord: */
15503     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15504     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15505     /* End of additions by Tord */
15506
15507     /* [HGM] added features: */
15508     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15509     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15510     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15511     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15512     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15513     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15514     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15515         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15516           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15517             SendToProgram(buf, cps);
15518             continue;
15519         }
15520         if(cps->nrOptions >= MAX_OPTIONS) {
15521             cps->nrOptions--;
15522             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15523             DisplayError(buf, 0);
15524         }
15525         continue;
15526     }
15527     /* End of additions by HGM */
15528
15529     /* unknown feature: complain and skip */
15530     q = p;
15531     while (*q && *q != '=') q++;
15532     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15533     SendToProgram(buf, cps);
15534     p = q;
15535     if (*p == '=') {
15536       p++;
15537       if (*p == '\"') {
15538         p++;
15539         while (*p && *p != '\"') p++;
15540         if (*p == '\"') p++;
15541       } else {
15542         while (*p && *p != ' ') p++;
15543       }
15544     }
15545   }
15546
15547 }
15548
15549 void
15550 PeriodicUpdatesEvent(newState)
15551      int newState;
15552 {
15553     if (newState == appData.periodicUpdates)
15554       return;
15555
15556     appData.periodicUpdates=newState;
15557
15558     /* Display type changes, so update it now */
15559 //    DisplayAnalysis();
15560
15561     /* Get the ball rolling again... */
15562     if (newState) {
15563         AnalysisPeriodicEvent(1);
15564         StartAnalysisClock();
15565     }
15566 }
15567
15568 void
15569 PonderNextMoveEvent(newState)
15570      int newState;
15571 {
15572     if (newState == appData.ponderNextMove) return;
15573     if (gameMode == EditPosition) EditPositionDone(TRUE);
15574     if (newState) {
15575         SendToProgram("hard\n", &first);
15576         if (gameMode == TwoMachinesPlay) {
15577             SendToProgram("hard\n", &second);
15578         }
15579     } else {
15580         SendToProgram("easy\n", &first);
15581         thinkOutput[0] = NULLCHAR;
15582         if (gameMode == TwoMachinesPlay) {
15583             SendToProgram("easy\n", &second);
15584         }
15585     }
15586     appData.ponderNextMove = newState;
15587 }
15588
15589 void
15590 NewSettingEvent(option, feature, command, value)
15591      char *command;
15592      int option, value, *feature;
15593 {
15594     char buf[MSG_SIZ];
15595
15596     if (gameMode == EditPosition) EditPositionDone(TRUE);
15597     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15598     if(feature == NULL || *feature) SendToProgram(buf, &first);
15599     if (gameMode == TwoMachinesPlay) {
15600         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15601     }
15602 }
15603
15604 void
15605 ShowThinkingEvent()
15606 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15607 {
15608     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15609     int newState = appData.showThinking
15610         // [HGM] thinking: other features now need thinking output as well
15611         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15612
15613     if (oldState == newState) return;
15614     oldState = newState;
15615     if (gameMode == EditPosition) EditPositionDone(TRUE);
15616     if (oldState) {
15617         SendToProgram("post\n", &first);
15618         if (gameMode == TwoMachinesPlay) {
15619             SendToProgram("post\n", &second);
15620         }
15621     } else {
15622         SendToProgram("nopost\n", &first);
15623         thinkOutput[0] = NULLCHAR;
15624         if (gameMode == TwoMachinesPlay) {
15625             SendToProgram("nopost\n", &second);
15626         }
15627     }
15628 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15629 }
15630
15631 void
15632 AskQuestionEvent(title, question, replyPrefix, which)
15633      char *title; char *question; char *replyPrefix; char *which;
15634 {
15635   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15636   if (pr == NoProc) return;
15637   AskQuestion(title, question, replyPrefix, pr);
15638 }
15639
15640 void
15641 TypeInEvent(char firstChar)
15642 {
15643     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15644         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15645         gameMode == AnalyzeMode || gameMode == EditGame || 
15646         gameMode == EditPosition || gameMode == IcsExamining ||
15647         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15648         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15649                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15650                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15651         gameMode == Training) PopUpMoveDialog(firstChar);
15652 }
15653
15654 void
15655 TypeInDoneEvent(char *move)
15656 {
15657         Board board;
15658         int n, fromX, fromY, toX, toY;
15659         char promoChar;
15660         ChessMove moveType;
15661
15662         // [HGM] FENedit
15663         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15664                 EditPositionPasteFEN(move);
15665                 return;
15666         }
15667         // [HGM] movenum: allow move number to be typed in any mode
15668         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15669           ToNrEvent(2*n-1);
15670           return;
15671         }
15672
15673       if (gameMode != EditGame && currentMove != forwardMostMove && 
15674         gameMode != Training) {
15675         DisplayMoveError(_("Displayed move is not current"));
15676       } else {
15677         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15678           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15679         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15680         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15681           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15682           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15683         } else {
15684           DisplayMoveError(_("Could not parse move"));
15685         }
15686       }
15687 }
15688
15689 void
15690 DisplayMove(moveNumber)
15691      int moveNumber;
15692 {
15693     char message[MSG_SIZ];
15694     char res[MSG_SIZ];
15695     char cpThinkOutput[MSG_SIZ];
15696
15697     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15698
15699     if (moveNumber == forwardMostMove - 1 ||
15700         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15701
15702         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15703
15704         if (strchr(cpThinkOutput, '\n')) {
15705             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15706         }
15707     } else {
15708         *cpThinkOutput = NULLCHAR;
15709     }
15710
15711     /* [AS] Hide thinking from human user */
15712     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15713         *cpThinkOutput = NULLCHAR;
15714         if( thinkOutput[0] != NULLCHAR ) {
15715             int i;
15716
15717             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15718                 cpThinkOutput[i] = '.';
15719             }
15720             cpThinkOutput[i] = NULLCHAR;
15721             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15722         }
15723     }
15724
15725     if (moveNumber == forwardMostMove - 1 &&
15726         gameInfo.resultDetails != NULL) {
15727         if (gameInfo.resultDetails[0] == NULLCHAR) {
15728           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15729         } else {
15730           snprintf(res, MSG_SIZ, " {%s} %s",
15731                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15732         }
15733     } else {
15734         res[0] = NULLCHAR;
15735     }
15736
15737     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15738         DisplayMessage(res, cpThinkOutput);
15739     } else {
15740       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15741                 WhiteOnMove(moveNumber) ? " " : ".. ",
15742                 parseList[moveNumber], res);
15743         DisplayMessage(message, cpThinkOutput);
15744     }
15745 }
15746
15747 void
15748 DisplayComment(moveNumber, text)
15749      int moveNumber;
15750      char *text;
15751 {
15752     char title[MSG_SIZ];
15753
15754     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15755       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15756     } else {
15757       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15758               WhiteOnMove(moveNumber) ? " " : ".. ",
15759               parseList[moveNumber]);
15760     }
15761     if (text != NULL && (appData.autoDisplayComment || commentUp))
15762         CommentPopUp(title, text);
15763 }
15764
15765 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15766  * might be busy thinking or pondering.  It can be omitted if your
15767  * gnuchess is configured to stop thinking immediately on any user
15768  * input.  However, that gnuchess feature depends on the FIONREAD
15769  * ioctl, which does not work properly on some flavors of Unix.
15770  */
15771 void
15772 Attention(cps)
15773      ChessProgramState *cps;
15774 {
15775 #if ATTENTION
15776     if (!cps->useSigint) return;
15777     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15778     switch (gameMode) {
15779       case MachinePlaysWhite:
15780       case MachinePlaysBlack:
15781       case TwoMachinesPlay:
15782       case IcsPlayingWhite:
15783       case IcsPlayingBlack:
15784       case AnalyzeMode:
15785       case AnalyzeFile:
15786         /* Skip if we know it isn't thinking */
15787         if (!cps->maybeThinking) return;
15788         if (appData.debugMode)
15789           fprintf(debugFP, "Interrupting %s\n", cps->which);
15790         InterruptChildProcess(cps->pr);
15791         cps->maybeThinking = FALSE;
15792         break;
15793       default:
15794         break;
15795     }
15796 #endif /*ATTENTION*/
15797 }
15798
15799 int
15800 CheckFlags()
15801 {
15802     if (whiteTimeRemaining <= 0) {
15803         if (!whiteFlag) {
15804             whiteFlag = TRUE;
15805             if (appData.icsActive) {
15806                 if (appData.autoCallFlag &&
15807                     gameMode == IcsPlayingBlack && !blackFlag) {
15808                   SendToICS(ics_prefix);
15809                   SendToICS("flag\n");
15810                 }
15811             } else {
15812                 if (blackFlag) {
15813                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15814                 } else {
15815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15816                     if (appData.autoCallFlag) {
15817                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15818                         return TRUE;
15819                     }
15820                 }
15821             }
15822         }
15823     }
15824     if (blackTimeRemaining <= 0) {
15825         if (!blackFlag) {
15826             blackFlag = TRUE;
15827             if (appData.icsActive) {
15828                 if (appData.autoCallFlag &&
15829                     gameMode == IcsPlayingWhite && !whiteFlag) {
15830                   SendToICS(ics_prefix);
15831                   SendToICS("flag\n");
15832                 }
15833             } else {
15834                 if (whiteFlag) {
15835                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15836                 } else {
15837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15838                     if (appData.autoCallFlag) {
15839                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15840                         return TRUE;
15841                     }
15842                 }
15843             }
15844         }
15845     }
15846     return FALSE;
15847 }
15848
15849 void
15850 CheckTimeControl()
15851 {
15852     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15853         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15854
15855     /*
15856      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15857      */
15858     if ( !WhiteOnMove(forwardMostMove) ) {
15859         /* White made time control */
15860         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15861         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15862         /* [HGM] time odds: correct new time quota for time odds! */
15863                                             / WhitePlayer()->timeOdds;
15864         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15865     } else {
15866         lastBlack -= blackTimeRemaining;
15867         /* Black made time control */
15868         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15869                                             / WhitePlayer()->other->timeOdds;
15870         lastWhite = whiteTimeRemaining;
15871     }
15872 }
15873
15874 void
15875 DisplayBothClocks()
15876 {
15877     int wom = gameMode == EditPosition ?
15878       !blackPlaysFirst : WhiteOnMove(currentMove);
15879     DisplayWhiteClock(whiteTimeRemaining, wom);
15880     DisplayBlackClock(blackTimeRemaining, !wom);
15881 }
15882
15883
15884 /* Timekeeping seems to be a portability nightmare.  I think everyone
15885    has ftime(), but I'm really not sure, so I'm including some ifdefs
15886    to use other calls if you don't.  Clocks will be less accurate if
15887    you have neither ftime nor gettimeofday.
15888 */
15889
15890 /* VS 2008 requires the #include outside of the function */
15891 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15892 #include <sys/timeb.h>
15893 #endif
15894
15895 /* Get the current time as a TimeMark */
15896 void
15897 GetTimeMark(tm)
15898      TimeMark *tm;
15899 {
15900 #if HAVE_GETTIMEOFDAY
15901
15902     struct timeval timeVal;
15903     struct timezone timeZone;
15904
15905     gettimeofday(&timeVal, &timeZone);
15906     tm->sec = (long) timeVal.tv_sec;
15907     tm->ms = (int) (timeVal.tv_usec / 1000L);
15908
15909 #else /*!HAVE_GETTIMEOFDAY*/
15910 #if HAVE_FTIME
15911
15912 // include <sys/timeb.h> / moved to just above start of function
15913     struct timeb timeB;
15914
15915     ftime(&timeB);
15916     tm->sec = (long) timeB.time;
15917     tm->ms = (int) timeB.millitm;
15918
15919 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15920     tm->sec = (long) time(NULL);
15921     tm->ms = 0;
15922 #endif
15923 #endif
15924 }
15925
15926 /* Return the difference in milliseconds between two
15927    time marks.  We assume the difference will fit in a long!
15928 */
15929 long
15930 SubtractTimeMarks(tm2, tm1)
15931      TimeMark *tm2, *tm1;
15932 {
15933     return 1000L*(tm2->sec - tm1->sec) +
15934            (long) (tm2->ms - tm1->ms);
15935 }
15936
15937
15938 /*
15939  * Code to manage the game clocks.
15940  *
15941  * In tournament play, black starts the clock and then white makes a move.
15942  * We give the human user a slight advantage if he is playing white---the
15943  * clocks don't run until he makes his first move, so it takes zero time.
15944  * Also, we don't account for network lag, so we could get out of sync
15945  * with GNU Chess's clock -- but then, referees are always right.
15946  */
15947
15948 static TimeMark tickStartTM;
15949 static long intendedTickLength;
15950
15951 long
15952 NextTickLength(timeRemaining)
15953      long timeRemaining;
15954 {
15955     long nominalTickLength, nextTickLength;
15956
15957     if (timeRemaining > 0L && timeRemaining <= 10000L)
15958       nominalTickLength = 100L;
15959     else
15960       nominalTickLength = 1000L;
15961     nextTickLength = timeRemaining % nominalTickLength;
15962     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15963
15964     return nextTickLength;
15965 }
15966
15967 /* Adjust clock one minute up or down */
15968 void
15969 AdjustClock(Boolean which, int dir)
15970 {
15971     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15972     if(which) blackTimeRemaining += 60000*dir;
15973     else      whiteTimeRemaining += 60000*dir;
15974     DisplayBothClocks();
15975     adjustedClock = TRUE;
15976 }
15977
15978 /* Stop clocks and reset to a fresh time control */
15979 void
15980 ResetClocks()
15981 {
15982     (void) StopClockTimer();
15983     if (appData.icsActive) {
15984         whiteTimeRemaining = blackTimeRemaining = 0;
15985     } else if (searchTime) {
15986         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15987         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15988     } else { /* [HGM] correct new time quote for time odds */
15989         whiteTC = blackTC = fullTimeControlString;
15990         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15991         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15992     }
15993     if (whiteFlag || blackFlag) {
15994         DisplayTitle("");
15995         whiteFlag = blackFlag = FALSE;
15996     }
15997     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15998     DisplayBothClocks();
15999     adjustedClock = FALSE;
16000 }
16001
16002 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16003
16004 /* Decrement running clock by amount of time that has passed */
16005 void
16006 DecrementClocks()
16007 {
16008     long timeRemaining;
16009     long lastTickLength, fudge;
16010     TimeMark now;
16011
16012     if (!appData.clockMode) return;
16013     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16014
16015     GetTimeMark(&now);
16016
16017     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16018
16019     /* Fudge if we woke up a little too soon */
16020     fudge = intendedTickLength - lastTickLength;
16021     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16022
16023     if (WhiteOnMove(forwardMostMove)) {
16024         if(whiteNPS >= 0) lastTickLength = 0;
16025         timeRemaining = whiteTimeRemaining -= lastTickLength;
16026         if(timeRemaining < 0 && !appData.icsActive) {
16027             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16028             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16029                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16030                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16031             }
16032         }
16033         DisplayWhiteClock(whiteTimeRemaining - fudge,
16034                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16035     } else {
16036         if(blackNPS >= 0) lastTickLength = 0;
16037         timeRemaining = blackTimeRemaining -= lastTickLength;
16038         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16039             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16040             if(suddenDeath) {
16041                 blackStartMove = forwardMostMove;
16042                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16043             }
16044         }
16045         DisplayBlackClock(blackTimeRemaining - fudge,
16046                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16047     }
16048     if (CheckFlags()) return;
16049
16050     tickStartTM = now;
16051     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16052     StartClockTimer(intendedTickLength);
16053
16054     /* if the time remaining has fallen below the alarm threshold, sound the
16055      * alarm. if the alarm has sounded and (due to a takeback or time control
16056      * with increment) the time remaining has increased to a level above the
16057      * threshold, reset the alarm so it can sound again.
16058      */
16059
16060     if (appData.icsActive && appData.icsAlarm) {
16061
16062         /* make sure we are dealing with the user's clock */
16063         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16064                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16065            )) return;
16066
16067         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16068             alarmSounded = FALSE;
16069         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16070             PlayAlarmSound();
16071             alarmSounded = TRUE;
16072         }
16073     }
16074 }
16075
16076
16077 /* A player has just moved, so stop the previously running
16078    clock and (if in clock mode) start the other one.
16079    We redisplay both clocks in case we're in ICS mode, because
16080    ICS gives us an update to both clocks after every move.
16081    Note that this routine is called *after* forwardMostMove
16082    is updated, so the last fractional tick must be subtracted
16083    from the color that is *not* on move now.
16084 */
16085 void
16086 SwitchClocks(int newMoveNr)
16087 {
16088     long lastTickLength;
16089     TimeMark now;
16090     int flagged = FALSE;
16091
16092     GetTimeMark(&now);
16093
16094     if (StopClockTimer() && appData.clockMode) {
16095         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16096         if (!WhiteOnMove(forwardMostMove)) {
16097             if(blackNPS >= 0) lastTickLength = 0;
16098             blackTimeRemaining -= lastTickLength;
16099            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16100 //         if(pvInfoList[forwardMostMove].time == -1)
16101                  pvInfoList[forwardMostMove].time =               // use GUI time
16102                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16103         } else {
16104            if(whiteNPS >= 0) lastTickLength = 0;
16105            whiteTimeRemaining -= lastTickLength;
16106            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16107 //         if(pvInfoList[forwardMostMove].time == -1)
16108                  pvInfoList[forwardMostMove].time =
16109                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16110         }
16111         flagged = CheckFlags();
16112     }
16113     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16114     CheckTimeControl();
16115
16116     if (flagged || !appData.clockMode) return;
16117
16118     switch (gameMode) {
16119       case MachinePlaysBlack:
16120       case MachinePlaysWhite:
16121       case BeginningOfGame:
16122         if (pausing) return;
16123         break;
16124
16125       case EditGame:
16126       case PlayFromGameFile:
16127       case IcsExamining:
16128         return;
16129
16130       default:
16131         break;
16132     }
16133
16134     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16135         if(WhiteOnMove(forwardMostMove))
16136              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16137         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16138     }
16139
16140     tickStartTM = now;
16141     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16142       whiteTimeRemaining : blackTimeRemaining);
16143     StartClockTimer(intendedTickLength);
16144 }
16145
16146
16147 /* Stop both clocks */
16148 void
16149 StopClocks()
16150 {
16151     long lastTickLength;
16152     TimeMark now;
16153
16154     if (!StopClockTimer()) return;
16155     if (!appData.clockMode) return;
16156
16157     GetTimeMark(&now);
16158
16159     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16160     if (WhiteOnMove(forwardMostMove)) {
16161         if(whiteNPS >= 0) lastTickLength = 0;
16162         whiteTimeRemaining -= lastTickLength;
16163         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16164     } else {
16165         if(blackNPS >= 0) lastTickLength = 0;
16166         blackTimeRemaining -= lastTickLength;
16167         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16168     }
16169     CheckFlags();
16170 }
16171
16172 /* Start clock of player on move.  Time may have been reset, so
16173    if clock is already running, stop and restart it. */
16174 void
16175 StartClocks()
16176 {
16177     (void) StopClockTimer(); /* in case it was running already */
16178     DisplayBothClocks();
16179     if (CheckFlags()) return;
16180
16181     if (!appData.clockMode) return;
16182     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16183
16184     GetTimeMark(&tickStartTM);
16185     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16186       whiteTimeRemaining : blackTimeRemaining);
16187
16188    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16189     whiteNPS = blackNPS = -1;
16190     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16191        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16192         whiteNPS = first.nps;
16193     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16194        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16195         blackNPS = first.nps;
16196     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16197         whiteNPS = second.nps;
16198     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16199         blackNPS = second.nps;
16200     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16201
16202     StartClockTimer(intendedTickLength);
16203 }
16204
16205 char *
16206 TimeString(ms)
16207      long ms;
16208 {
16209     long second, minute, hour, day;
16210     char *sign = "";
16211     static char buf[32];
16212
16213     if (ms > 0 && ms <= 9900) {
16214       /* convert milliseconds to tenths, rounding up */
16215       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16216
16217       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16218       return buf;
16219     }
16220
16221     /* convert milliseconds to seconds, rounding up */
16222     /* use floating point to avoid strangeness of integer division
16223        with negative dividends on many machines */
16224     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16225
16226     if (second < 0) {
16227         sign = "-";
16228         second = -second;
16229     }
16230
16231     day = second / (60 * 60 * 24);
16232     second = second % (60 * 60 * 24);
16233     hour = second / (60 * 60);
16234     second = second % (60 * 60);
16235     minute = second / 60;
16236     second = second % 60;
16237
16238     if (day > 0)
16239       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16240               sign, day, hour, minute, second);
16241     else if (hour > 0)
16242       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16243     else
16244       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16245
16246     return buf;
16247 }
16248
16249
16250 /*
16251  * This is necessary because some C libraries aren't ANSI C compliant yet.
16252  */
16253 char *
16254 StrStr(string, match)
16255      char *string, *match;
16256 {
16257     int i, length;
16258
16259     length = strlen(match);
16260
16261     for (i = strlen(string) - length; i >= 0; i--, string++)
16262       if (!strncmp(match, string, length))
16263         return string;
16264
16265     return NULL;
16266 }
16267
16268 char *
16269 StrCaseStr(string, match)
16270      char *string, *match;
16271 {
16272     int i, j, length;
16273
16274     length = strlen(match);
16275
16276     for (i = strlen(string) - length; i >= 0; i--, string++) {
16277         for (j = 0; j < length; j++) {
16278             if (ToLower(match[j]) != ToLower(string[j]))
16279               break;
16280         }
16281         if (j == length) return string;
16282     }
16283
16284     return NULL;
16285 }
16286
16287 #ifndef _amigados
16288 int
16289 StrCaseCmp(s1, s2)
16290      char *s1, *s2;
16291 {
16292     char c1, c2;
16293
16294     for (;;) {
16295         c1 = ToLower(*s1++);
16296         c2 = ToLower(*s2++);
16297         if (c1 > c2) return 1;
16298         if (c1 < c2) return -1;
16299         if (c1 == NULLCHAR) return 0;
16300     }
16301 }
16302
16303
16304 int
16305 ToLower(c)
16306      int c;
16307 {
16308     return isupper(c) ? tolower(c) : c;
16309 }
16310
16311
16312 int
16313 ToUpper(c)
16314      int c;
16315 {
16316     return islower(c) ? toupper(c) : c;
16317 }
16318 #endif /* !_amigados    */
16319
16320 char *
16321 StrSave(s)
16322      char *s;
16323 {
16324   char *ret;
16325
16326   if ((ret = (char *) malloc(strlen(s) + 1)))
16327     {
16328       safeStrCpy(ret, s, strlen(s)+1);
16329     }
16330   return ret;
16331 }
16332
16333 char *
16334 StrSavePtr(s, savePtr)
16335      char *s, **savePtr;
16336 {
16337     if (*savePtr) {
16338         free(*savePtr);
16339     }
16340     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16341       safeStrCpy(*savePtr, s, strlen(s)+1);
16342     }
16343     return(*savePtr);
16344 }
16345
16346 char *
16347 PGNDate()
16348 {
16349     time_t clock;
16350     struct tm *tm;
16351     char buf[MSG_SIZ];
16352
16353     clock = time((time_t *)NULL);
16354     tm = localtime(&clock);
16355     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16356             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16357     return StrSave(buf);
16358 }
16359
16360
16361 char *
16362 PositionToFEN(move, overrideCastling)
16363      int move;
16364      char *overrideCastling;
16365 {
16366     int i, j, fromX, fromY, toX, toY;
16367     int whiteToPlay;
16368     char buf[MSG_SIZ];
16369     char *p, *q;
16370     int emptycount;
16371     ChessSquare piece;
16372
16373     whiteToPlay = (gameMode == EditPosition) ?
16374       !blackPlaysFirst : (move % 2 == 0);
16375     p = buf;
16376
16377     /* Piece placement data */
16378     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16379         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16380         emptycount = 0;
16381         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16382             if (boards[move][i][j] == EmptySquare) {
16383                 emptycount++;
16384             } else { ChessSquare piece = boards[move][i][j];
16385                 if (emptycount > 0) {
16386                     if(emptycount<10) /* [HGM] can be >= 10 */
16387                         *p++ = '0' + emptycount;
16388                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16389                     emptycount = 0;
16390                 }
16391                 if(PieceToChar(piece) == '+') {
16392                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16393                     *p++ = '+';
16394                     piece = (ChessSquare)(DEMOTED piece);
16395                 }
16396                 *p++ = PieceToChar(piece);
16397                 if(p[-1] == '~') {
16398                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16399                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16400                     *p++ = '~';
16401                 }
16402             }
16403         }
16404         if (emptycount > 0) {
16405             if(emptycount<10) /* [HGM] can be >= 10 */
16406                 *p++ = '0' + emptycount;
16407             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16408             emptycount = 0;
16409         }
16410         *p++ = '/';
16411     }
16412     *(p - 1) = ' ';
16413
16414     /* [HGM] print Crazyhouse or Shogi holdings */
16415     if( gameInfo.holdingsWidth ) {
16416         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16417         q = p;
16418         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16419             piece = boards[move][i][BOARD_WIDTH-1];
16420             if( piece != EmptySquare )
16421               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16422                   *p++ = PieceToChar(piece);
16423         }
16424         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16425             piece = boards[move][BOARD_HEIGHT-i-1][0];
16426             if( piece != EmptySquare )
16427               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16428                   *p++ = PieceToChar(piece);
16429         }
16430
16431         if( q == p ) *p++ = '-';
16432         *p++ = ']';
16433         *p++ = ' ';
16434     }
16435
16436     /* Active color */
16437     *p++ = whiteToPlay ? 'w' : 'b';
16438     *p++ = ' ';
16439
16440   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16441     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16442   } else {
16443   if(nrCastlingRights) {
16444      q = p;
16445      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16446        /* [HGM] write directly from rights */
16447            if(boards[move][CASTLING][2] != NoRights &&
16448               boards[move][CASTLING][0] != NoRights   )
16449                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16450            if(boards[move][CASTLING][2] != NoRights &&
16451               boards[move][CASTLING][1] != NoRights   )
16452                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16453            if(boards[move][CASTLING][5] != NoRights &&
16454               boards[move][CASTLING][3] != NoRights   )
16455                 *p++ = boards[move][CASTLING][3] + AAA;
16456            if(boards[move][CASTLING][5] != NoRights &&
16457               boards[move][CASTLING][4] != NoRights   )
16458                 *p++ = boards[move][CASTLING][4] + AAA;
16459      } else {
16460
16461         /* [HGM] write true castling rights */
16462         if( nrCastlingRights == 6 ) {
16463             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16464                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16465             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16466                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16467             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16468                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16469             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16470                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16471         }
16472      }
16473      if (q == p) *p++ = '-'; /* No castling rights */
16474      *p++ = ' ';
16475   }
16476
16477   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16478      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16479     /* En passant target square */
16480     if (move > backwardMostMove) {
16481         fromX = moveList[move - 1][0] - AAA;
16482         fromY = moveList[move - 1][1] - ONE;
16483         toX = moveList[move - 1][2] - AAA;
16484         toY = moveList[move - 1][3] - ONE;
16485         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16486             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16487             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16488             fromX == toX) {
16489             /* 2-square pawn move just happened */
16490             *p++ = toX + AAA;
16491             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16492         } else {
16493             *p++ = '-';
16494         }
16495     } else if(move == backwardMostMove) {
16496         // [HGM] perhaps we should always do it like this, and forget the above?
16497         if((signed char)boards[move][EP_STATUS] >= 0) {
16498             *p++ = boards[move][EP_STATUS] + AAA;
16499             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16500         } else {
16501             *p++ = '-';
16502         }
16503     } else {
16504         *p++ = '-';
16505     }
16506     *p++ = ' ';
16507   }
16508   }
16509
16510     /* [HGM] find reversible plies */
16511     {   int i = 0, j=move;
16512
16513         if (appData.debugMode) { int k;
16514             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16515             for(k=backwardMostMove; k<=forwardMostMove; k++)
16516                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16517
16518         }
16519
16520         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16521         if( j == backwardMostMove ) i += initialRulePlies;
16522         sprintf(p, "%d ", i);
16523         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16524     }
16525     /* Fullmove number */
16526     sprintf(p, "%d", (move / 2) + 1);
16527
16528     return StrSave(buf);
16529 }
16530
16531 Boolean
16532 ParseFEN(board, blackPlaysFirst, fen)
16533     Board board;
16534      int *blackPlaysFirst;
16535      char *fen;
16536 {
16537     int i, j;
16538     char *p, c;
16539     int emptycount;
16540     ChessSquare piece;
16541
16542     p = fen;
16543
16544     /* [HGM] by default clear Crazyhouse holdings, if present */
16545     if(gameInfo.holdingsWidth) {
16546        for(i=0; i<BOARD_HEIGHT; i++) {
16547            board[i][0]             = EmptySquare; /* black holdings */
16548            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16549            board[i][1]             = (ChessSquare) 0; /* black counts */
16550            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16551        }
16552     }
16553
16554     /* Piece placement data */
16555     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16556         j = 0;
16557         for (;;) {
16558             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16559                 if (*p == '/') p++;
16560                 emptycount = gameInfo.boardWidth - j;
16561                 while (emptycount--)
16562                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16563                 break;
16564 #if(BOARD_FILES >= 10)
16565             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16566                 p++; emptycount=10;
16567                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16568                 while (emptycount--)
16569                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16570 #endif
16571             } else if (isdigit(*p)) {
16572                 emptycount = *p++ - '0';
16573                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16574                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16575                 while (emptycount--)
16576                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16577             } else if (*p == '+' || isalpha(*p)) {
16578                 if (j >= gameInfo.boardWidth) return FALSE;
16579                 if(*p=='+') {
16580                     piece = CharToPiece(*++p);
16581                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16582                     piece = (ChessSquare) (PROMOTED piece ); p++;
16583                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16584                 } else piece = CharToPiece(*p++);
16585
16586                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16587                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16588                     piece = (ChessSquare) (PROMOTED piece);
16589                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16590                     p++;
16591                 }
16592                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16593             } else {
16594                 return FALSE;
16595             }
16596         }
16597     }
16598     while (*p == '/' || *p == ' ') p++;
16599
16600     /* [HGM] look for Crazyhouse holdings here */
16601     while(*p==' ') p++;
16602     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16603         if(*p == '[') p++;
16604         if(*p == '-' ) p++; /* empty holdings */ else {
16605             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16606             /* if we would allow FEN reading to set board size, we would   */
16607             /* have to add holdings and shift the board read so far here   */
16608             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16609                 p++;
16610                 if((int) piece >= (int) BlackPawn ) {
16611                     i = (int)piece - (int)BlackPawn;
16612                     i = PieceToNumber((ChessSquare)i);
16613                     if( i >= gameInfo.holdingsSize ) return FALSE;
16614                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16615                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16616                 } else {
16617                     i = (int)piece - (int)WhitePawn;
16618                     i = PieceToNumber((ChessSquare)i);
16619                     if( i >= gameInfo.holdingsSize ) return FALSE;
16620                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16621                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16622                 }
16623             }
16624         }
16625         if(*p == ']') p++;
16626     }
16627
16628     while(*p == ' ') p++;
16629
16630     /* Active color */
16631     c = *p++;
16632     if(appData.colorNickNames) {
16633       if( c == appData.colorNickNames[0] ) c = 'w'; else
16634       if( c == appData.colorNickNames[1] ) c = 'b';
16635     }
16636     switch (c) {
16637       case 'w':
16638         *blackPlaysFirst = FALSE;
16639         break;
16640       case 'b':
16641         *blackPlaysFirst = TRUE;
16642         break;
16643       default:
16644         return FALSE;
16645     }
16646
16647     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16648     /* return the extra info in global variiables             */
16649
16650     /* set defaults in case FEN is incomplete */
16651     board[EP_STATUS] = EP_UNKNOWN;
16652     for(i=0; i<nrCastlingRights; i++ ) {
16653         board[CASTLING][i] =
16654             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16655     }   /* assume possible unless obviously impossible */
16656     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16657     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16658     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16659                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16660     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16661     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16662     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16663                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16664     FENrulePlies = 0;
16665
16666     while(*p==' ') p++;
16667     if(nrCastlingRights) {
16668       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16669           /* castling indicator present, so default becomes no castlings */
16670           for(i=0; i<nrCastlingRights; i++ ) {
16671                  board[CASTLING][i] = NoRights;
16672           }
16673       }
16674       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16675              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16676              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16677              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16678         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16679
16680         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16681             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16682             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16683         }
16684         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16685             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16686         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16687                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16688         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16689                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16690         switch(c) {
16691           case'K':
16692               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16693               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16694               board[CASTLING][2] = whiteKingFile;
16695               break;
16696           case'Q':
16697               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16698               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16699               board[CASTLING][2] = whiteKingFile;
16700               break;
16701           case'k':
16702               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16703               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16704               board[CASTLING][5] = blackKingFile;
16705               break;
16706           case'q':
16707               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16708               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16709               board[CASTLING][5] = blackKingFile;
16710           case '-':
16711               break;
16712           default: /* FRC castlings */
16713               if(c >= 'a') { /* black rights */
16714                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16715                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16716                   if(i == BOARD_RGHT) break;
16717                   board[CASTLING][5] = i;
16718                   c -= AAA;
16719                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16720                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16721                   if(c > i)
16722                       board[CASTLING][3] = c;
16723                   else
16724                       board[CASTLING][4] = c;
16725               } else { /* white rights */
16726                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16727                     if(board[0][i] == WhiteKing) break;
16728                   if(i == BOARD_RGHT) break;
16729                   board[CASTLING][2] = i;
16730                   c -= AAA - 'a' + 'A';
16731                   if(board[0][c] >= WhiteKing) break;
16732                   if(c > i)
16733                       board[CASTLING][0] = c;
16734                   else
16735                       board[CASTLING][1] = c;
16736               }
16737         }
16738       }
16739       for(i=0; i<nrCastlingRights; i++)
16740         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16741     if (appData.debugMode) {
16742         fprintf(debugFP, "FEN castling rights:");
16743         for(i=0; i<nrCastlingRights; i++)
16744         fprintf(debugFP, " %d", board[CASTLING][i]);
16745         fprintf(debugFP, "\n");
16746     }
16747
16748       while(*p==' ') p++;
16749     }
16750
16751     /* read e.p. field in games that know e.p. capture */
16752     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16753        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16754       if(*p=='-') {
16755         p++; board[EP_STATUS] = EP_NONE;
16756       } else {
16757          char c = *p++ - AAA;
16758
16759          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16760          if(*p >= '0' && *p <='9') p++;
16761          board[EP_STATUS] = c;
16762       }
16763     }
16764
16765
16766     if(sscanf(p, "%d", &i) == 1) {
16767         FENrulePlies = i; /* 50-move ply counter */
16768         /* (The move number is still ignored)    */
16769     }
16770
16771     return TRUE;
16772 }
16773
16774 void
16775 EditPositionPasteFEN(char *fen)
16776 {
16777   if (fen != NULL) {
16778     Board initial_position;
16779
16780     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16781       DisplayError(_("Bad FEN position in clipboard"), 0);
16782       return ;
16783     } else {
16784       int savedBlackPlaysFirst = blackPlaysFirst;
16785       EditPositionEvent();
16786       blackPlaysFirst = savedBlackPlaysFirst;
16787       CopyBoard(boards[0], initial_position);
16788       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16789       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16790       DisplayBothClocks();
16791       DrawPosition(FALSE, boards[currentMove]);
16792     }
16793   }
16794 }
16795
16796 static char cseq[12] = "\\   ";
16797
16798 Boolean set_cont_sequence(char *new_seq)
16799 {
16800     int len;
16801     Boolean ret;
16802
16803     // handle bad attempts to set the sequence
16804         if (!new_seq)
16805                 return 0; // acceptable error - no debug
16806
16807     len = strlen(new_seq);
16808     ret = (len > 0) && (len < sizeof(cseq));
16809     if (ret)
16810       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16811     else if (appData.debugMode)
16812       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16813     return ret;
16814 }
16815
16816 /*
16817     reformat a source message so words don't cross the width boundary.  internal
16818     newlines are not removed.  returns the wrapped size (no null character unless
16819     included in source message).  If dest is NULL, only calculate the size required
16820     for the dest buffer.  lp argument indicats line position upon entry, and it's
16821     passed back upon exit.
16822 */
16823 int wrap(char *dest, char *src, int count, int width, int *lp)
16824 {
16825     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16826
16827     cseq_len = strlen(cseq);
16828     old_line = line = *lp;
16829     ansi = len = clen = 0;
16830
16831     for (i=0; i < count; i++)
16832     {
16833         if (src[i] == '\033')
16834             ansi = 1;
16835
16836         // if we hit the width, back up
16837         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16838         {
16839             // store i & len in case the word is too long
16840             old_i = i, old_len = len;
16841
16842             // find the end of the last word
16843             while (i && src[i] != ' ' && src[i] != '\n')
16844             {
16845                 i--;
16846                 len--;
16847             }
16848
16849             // word too long?  restore i & len before splitting it
16850             if ((old_i-i+clen) >= width)
16851             {
16852                 i = old_i;
16853                 len = old_len;
16854             }
16855
16856             // extra space?
16857             if (i && src[i-1] == ' ')
16858                 len--;
16859
16860             if (src[i] != ' ' && src[i] != '\n')
16861             {
16862                 i--;
16863                 if (len)
16864                     len--;
16865             }
16866
16867             // now append the newline and continuation sequence
16868             if (dest)
16869                 dest[len] = '\n';
16870             len++;
16871             if (dest)
16872                 strncpy(dest+len, cseq, cseq_len);
16873             len += cseq_len;
16874             line = cseq_len;
16875             clen = cseq_len;
16876             continue;
16877         }
16878
16879         if (dest)
16880             dest[len] = src[i];
16881         len++;
16882         if (!ansi)
16883             line++;
16884         if (src[i] == '\n')
16885             line = 0;
16886         if (src[i] == 'm')
16887             ansi = 0;
16888     }
16889     if (dest && appData.debugMode)
16890     {
16891         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16892             count, width, line, len, *lp);
16893         show_bytes(debugFP, src, count);
16894         fprintf(debugFP, "\ndest: ");
16895         show_bytes(debugFP, dest, len);
16896         fprintf(debugFP, "\n");
16897     }
16898     *lp = dest ? line : old_line;
16899
16900     return len;
16901 }
16902
16903 // [HGM] vari: routines for shelving variations
16904 Boolean modeRestore = FALSE;
16905
16906 void
16907 PushInner(int firstMove, int lastMove)
16908 {
16909         int i, j, nrMoves = lastMove - firstMove;
16910
16911         // push current tail of game on stack
16912         savedResult[storedGames] = gameInfo.result;
16913         savedDetails[storedGames] = gameInfo.resultDetails;
16914         gameInfo.resultDetails = NULL;
16915         savedFirst[storedGames] = firstMove;
16916         savedLast [storedGames] = lastMove;
16917         savedFramePtr[storedGames] = framePtr;
16918         framePtr -= nrMoves; // reserve space for the boards
16919         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16920             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16921             for(j=0; j<MOVE_LEN; j++)
16922                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16923             for(j=0; j<2*MOVE_LEN; j++)
16924                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16925             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16926             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16927             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16928             pvInfoList[firstMove+i-1].depth = 0;
16929             commentList[framePtr+i] = commentList[firstMove+i];
16930             commentList[firstMove+i] = NULL;
16931         }
16932
16933         storedGames++;
16934         forwardMostMove = firstMove; // truncate game so we can start variation
16935 }
16936
16937 void
16938 PushTail(int firstMove, int lastMove)
16939 {
16940         if(appData.icsActive) { // only in local mode
16941                 forwardMostMove = currentMove; // mimic old ICS behavior
16942                 return;
16943         }
16944         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16945
16946         PushInner(firstMove, lastMove);
16947         if(storedGames == 1) GreyRevert(FALSE);
16948         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16949 }
16950
16951 void
16952 PopInner(Boolean annotate)
16953 {
16954         int i, j, nrMoves;
16955         char buf[8000], moveBuf[20];
16956
16957         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16958         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16959         nrMoves = savedLast[storedGames] - currentMove;
16960         if(annotate) {
16961                 int cnt = 10;
16962                 if(!WhiteOnMove(currentMove))
16963                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16964                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16965                 for(i=currentMove; i<forwardMostMove; i++) {
16966                         if(WhiteOnMove(i))
16967                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16968                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16969                         strcat(buf, moveBuf);
16970                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16971                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16972                 }
16973                 strcat(buf, ")");
16974         }
16975         for(i=1; i<=nrMoves; i++) { // copy last variation back
16976             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16977             for(j=0; j<MOVE_LEN; j++)
16978                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16979             for(j=0; j<2*MOVE_LEN; j++)
16980                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16981             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16982             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16983             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16984             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16985             commentList[currentMove+i] = commentList[framePtr+i];
16986             commentList[framePtr+i] = NULL;
16987         }
16988         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16989         framePtr = savedFramePtr[storedGames];
16990         gameInfo.result = savedResult[storedGames];
16991         if(gameInfo.resultDetails != NULL) {
16992             free(gameInfo.resultDetails);
16993       }
16994         gameInfo.resultDetails = savedDetails[storedGames];
16995         forwardMostMove = currentMove + nrMoves;
16996 }
16997
16998 Boolean
16999 PopTail(Boolean annotate)
17000 {
17001         if(appData.icsActive) return FALSE; // only in local mode
17002         if(!storedGames) return FALSE; // sanity
17003         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17004
17005         PopInner(annotate);
17006         if(currentMove < forwardMostMove) ForwardEvent(); else
17007         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17008
17009         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17010         return TRUE;
17011 }
17012
17013 void
17014 CleanupTail()
17015 {       // remove all shelved variations
17016         int i;
17017         for(i=0; i<storedGames; i++) {
17018             if(savedDetails[i])
17019                 free(savedDetails[i]);
17020             savedDetails[i] = NULL;
17021         }
17022         for(i=framePtr; i<MAX_MOVES; i++) {
17023                 if(commentList[i]) free(commentList[i]);
17024                 commentList[i] = NULL;
17025         }
17026         framePtr = MAX_MOVES-1;
17027         storedGames = 0;
17028 }
17029
17030 void
17031 LoadVariation(int index, char *text)
17032 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17033         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17034         int level = 0, move;
17035
17036         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17037         // first find outermost bracketing variation
17038         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17039             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17040                 if(*p == '{') wait = '}'; else
17041                 if(*p == '[') wait = ']'; else
17042                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17043                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17044             }
17045             if(*p == wait) wait = NULLCHAR; // closing ]} found
17046             p++;
17047         }
17048         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17049         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17050         end[1] = NULLCHAR; // clip off comment beyond variation
17051         ToNrEvent(currentMove-1);
17052         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17053         // kludge: use ParsePV() to append variation to game
17054         move = currentMove;
17055         ParsePV(start, TRUE, TRUE);
17056         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17057         ClearPremoveHighlights();
17058         CommentPopDown();
17059         ToNrEvent(currentMove+1);
17060 }
17061