Add option -discourageOwnBooks
[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 ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397   case VariantGrand:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 Boolean shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
606 };
607
608 #ifdef GOTHIC
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !GOTHIC
616 #define GothicArray CapablancaArray
617 #endif // !GOTHIC
618
619 #ifdef FALCON
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
622         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
624         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
625 };
626 #else // !FALCON
627 #define FalconArray CapablancaArray
628 #endif // !FALCON
629
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
636
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
643 };
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
647
648
649 Board initialPosition;
650
651
652 /* Convert str to a rating. Checks for special cases of "----",
653
654    "++++", etc. Also strips ()'s */
655 int
656 string_to_rating(str)
657   char *str;
658 {
659   while(*str && !isdigit(*str)) ++str;
660   if (!*str)
661     return 0;   /* One of the special "no rating" cases */
662   else
663     return atoi(str);
664 }
665
666 void
667 ClearProgramStats()
668 {
669     /* Init programStats */
670     programStats.movelist[0] = 0;
671     programStats.depth = 0;
672     programStats.nr_moves = 0;
673     programStats.moves_left = 0;
674     programStats.nodes = 0;
675     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
676     programStats.score = 0;
677     programStats.got_only_move = 0;
678     programStats.got_fail = 0;
679     programStats.line_is_book = 0;
680 }
681
682 void
683 CommonEngineInit()
684 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685     if (appData.firstPlaysBlack) {
686         first.twoMachinesColor = "black\n";
687         second.twoMachinesColor = "white\n";
688     } else {
689         first.twoMachinesColor = "white\n";
690         second.twoMachinesColor = "black\n";
691     }
692
693     first.other = &second;
694     second.other = &first;
695
696     { float norm = 1;
697         if(appData.timeOddsMode) {
698             norm = appData.timeOdds[0];
699             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
700         }
701         first.timeOdds  = appData.timeOdds[0]/norm;
702         second.timeOdds = appData.timeOdds[1]/norm;
703     }
704
705     if(programVersion) free(programVersion);
706     if (appData.noChessProgram) {
707         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708         sprintf(programVersion, "%s", PACKAGE_STRING);
709     } else {
710       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
713     }
714 }
715
716 void
717 UnloadEngine(ChessProgramState *cps)
718 {
719         /* Kill off first chess program */
720         if (cps->isr != NULL)
721           RemoveInputSource(cps->isr);
722         cps->isr = NULL;
723
724         if (cps->pr != NoProc) {
725             ExitAnalyzeMode();
726             DoSleep( appData.delayBeforeQuit );
727             SendToProgram("quit\n", cps);
728             DoSleep( appData.delayAfterQuit );
729             DestroyChildProcess(cps->pr, cps->useSigterm);
730         }
731         cps->pr = NoProc;
732         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
733 }
734
735 void
736 ClearOptions(ChessProgramState *cps)
737 {
738     int i;
739     cps->nrOptions = cps->comboCnt = 0;
740     for(i=0; i<MAX_OPTIONS; i++) {
741         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742         cps->option[i].textValue = 0;
743     }
744 }
745
746 char *engineNames[] = {
747 "first",
748 "second"
749 };
750
751 void
752 InitEngine(ChessProgramState *cps, int n)
753 {   // [HGM] all engine initialiation put in a function that does one engine
754
755     ClearOptions(cps);
756
757     cps->which = engineNames[n];
758     cps->maybeThinking = FALSE;
759     cps->pr = NoProc;
760     cps->isr = NULL;
761     cps->sendTime = 2;
762     cps->sendDrawOffers = 1;
763
764     cps->program = appData.chessProgram[n];
765     cps->host = appData.host[n];
766     cps->dir = appData.directory[n];
767     cps->initString = appData.engInitString[n];
768     cps->computerString = appData.computerString[n];
769     cps->useSigint  = TRUE;
770     cps->useSigterm = TRUE;
771     cps->reuse = appData.reuse[n];
772     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
773     cps->useSetboard = FALSE;
774     cps->useSAN = FALSE;
775     cps->usePing = FALSE;
776     cps->lastPing = 0;
777     cps->lastPong = 0;
778     cps->usePlayother = FALSE;
779     cps->useColors = TRUE;
780     cps->useUsermove = FALSE;
781     cps->sendICS = FALSE;
782     cps->sendName = appData.icsActive;
783     cps->sdKludge = FALSE;
784     cps->stKludge = FALSE;
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     cps->egtFormats[0] = NULLCHAR;
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len > MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine(ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] = 
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
884
885 void
886 Load(ChessProgramState *cps, int i)
887 {
888     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
889     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
890         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
891         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
892         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
893         ParseArgsFromString(buf);
894         SwapEngines(i);
895         ReplaceEngine(cps, i);
896         return;
897     }
898     p = engineName;
899     while(q = strchr(p, SLASH)) p = q+1;
900     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
901     if(engineDir[0] != NULLCHAR)
902         appData.directory[i] = engineDir;
903     else if(p != engineName) { // derive directory from engine path, when not given
904         p[-1] = 0;
905         appData.directory[i] = strdup(engineName);
906         p[-1] = SLASH;
907     } else appData.directory[i] = ".";
908     if(params[0]) {
909         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
910         snprintf(command, MSG_SIZ, "%s %s", p, params);
911         p = command;
912     }
913     appData.chessProgram[i] = strdup(p);
914     appData.isUCI[i] = isUCI;
915     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
916     appData.hasOwnBookUCI[i] = hasBook;
917     if(!nickName[0]) useNick = FALSE;
918     if(useNick) ASSIGN(appData.pgnName[i], nickName);
919     if(addToList) {
920         int len;
921         char quote;
922         q = firstChessProgramNames;
923         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
924         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
926                         quote, p, quote, appData.directory[i], 
927                         useNick ? " -fn \"" : "",
928                         useNick ? nickName : "",
929                         useNick ? "\"" : "",
930                         v1 ? " -firstProtocolVersion 1" : "",
931                         hasBook ? "" : " -fNoOwnBookUCI",
932                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
933                         storeVariant ? " -variant " : "",
934                         storeVariant ? VariantName(gameInfo.variant) : "");
935         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
936         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
937         if(q)   free(q);
938     }
939     ReplaceEngine(cps, i);
940 }
941
942 void
943 InitTimeControls()
944 {
945     int matched, min, sec;
946     /*
947      * Parse timeControl resource
948      */
949     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
950                           appData.movesPerSession)) {
951         char buf[MSG_SIZ];
952         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
953         DisplayFatalError(buf, 0, 2);
954     }
955
956     /*
957      * Parse searchTime resource
958      */
959     if (*appData.searchTime != NULLCHAR) {
960         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
961         if (matched == 1) {
962             searchTime = min * 60;
963         } else if (matched == 2) {
964             searchTime = min * 60 + sec;
965         } else {
966             char buf[MSG_SIZ];
967             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
968             DisplayFatalError(buf, 0, 2);
969         }
970     }
971 }
972
973 void
974 InitBackEnd1()
975 {
976
977     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
978     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
979
980     GetTimeMark(&programStartTime);
981     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
982     appData.seedBase = random() + (random()<<15);
983     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984
985     ClearProgramStats();
986     programStats.ok_to_send = 1;
987     programStats.seen_stat = 0;
988
989     /*
990      * Initialize game list
991      */
992     ListNew(&gameList);
993
994
995     /*
996      * Internet chess server status
997      */
998     if (appData.icsActive) {
999         appData.matchMode = FALSE;
1000         appData.matchGames = 0;
1001 #if ZIPPY
1002         appData.noChessProgram = !appData.zippyPlay;
1003 #else
1004         appData.zippyPlay = FALSE;
1005         appData.zippyTalk = FALSE;
1006         appData.noChessProgram = TRUE;
1007 #endif
1008         if (*appData.icsHelper != NULLCHAR) {
1009             appData.useTelnet = TRUE;
1010             appData.telnetProgram = appData.icsHelper;
1011         }
1012     } else {
1013         appData.zippyTalk = appData.zippyPlay = FALSE;
1014     }
1015
1016     /* [AS] Initialize pv info list [HGM] and game state */
1017     {
1018         int i, j;
1019
1020         for( i=0; i<=framePtr; i++ ) {
1021             pvInfoList[i].depth = -1;
1022             boards[i][EP_STATUS] = EP_NONE;
1023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1024         }
1025     }
1026
1027     InitTimeControls();
1028
1029     /* [AS] Adjudication threshold */
1030     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031
1032     InitEngine(&first, 0);
1033     InitEngine(&second, 1);
1034     CommonEngineInit();
1035
1036     pairing.which = "pairing"; // pairing engine
1037     pairing.pr = NoProc;
1038     pairing.isr = NULL;
1039     pairing.program = appData.pairingEngine;
1040     pairing.host = "localhost";
1041     pairing.dir = ".";
1042
1043     if (appData.icsActive) {
1044         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1045     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046         appData.clockMode = FALSE;
1047         first.sendTime = second.sendTime = 0;
1048     }
1049
1050 #if ZIPPY
1051     /* Override some settings from environment variables, for backward
1052        compatibility.  Unfortunately it's not feasible to have the env
1053        vars just set defaults, at least in xboard.  Ugh.
1054     */
1055     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1056       ZippyInit();
1057     }
1058 #endif
1059
1060     if (!appData.icsActive) {
1061       char buf[MSG_SIZ];
1062       int len;
1063
1064       /* Check for variants that are supported only in ICS mode,
1065          or not at all.  Some that are accepted here nevertheless
1066          have bugs; see comments below.
1067       */
1068       VariantClass variant = StringToVariant(appData.variant);
1069       switch (variant) {
1070       case VariantBughouse:     /* need four players and two boards */
1071       case VariantKriegspiel:   /* need to hide pieces and move details */
1072         /* case VariantFischeRandom: (Fabien: moved below) */
1073         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074         if( (len > MSG_SIZ) && appData.debugMode )
1075           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076
1077         DisplayFatalError(buf, 0, 2);
1078         return;
1079
1080       case VariantUnknown:
1081       case VariantLoadable:
1082       case Variant29:
1083       case Variant30:
1084       case Variant31:
1085       case Variant32:
1086       case Variant33:
1087       case Variant34:
1088       case Variant35:
1089       case Variant36:
1090       default:
1091         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092         if( (len > MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1099       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1100       case VariantGothic:     /* [HGM] should work */
1101       case VariantCapablanca: /* [HGM] should work */
1102       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1103       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1104       case VariantKnightmate: /* [HGM] should work */
1105       case VariantCylinder:   /* [HGM] untested */
1106       case VariantFalcon:     /* [HGM] untested */
1107       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108                                  offboard interposition not understood */
1109       case VariantNormal:     /* definitely works! */
1110       case VariantWildCastle: /* pieces not automatically shuffled */
1111       case VariantNoCastle:   /* pieces not automatically shuffled */
1112       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113       case VariantLosers:     /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantSuicide:    /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantGiveaway:   /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantTwoKings:   /* should work */
1120       case VariantAtomic:     /* should work except for win condition */
1121       case Variant3Check:     /* should work except for win condition */
1122       case VariantShatranj:   /* should work except for all win conditions */
1123       case VariantMakruk:     /* should work except for draw countdown */
1124       case VariantBerolina:   /* might work if TestLegality is off */
1125       case VariantCapaRandom: /* should work */
1126       case VariantJanus:      /* should work */
1127       case VariantSuper:      /* experimental */
1128       case VariantGreat:      /* experimental, requires legality testing to be off */
1129       case VariantSChess:     /* S-Chess, should work */
1130       case VariantGrand:      /* should work */
1131       case VariantSpartan:    /* should work */
1132         break;
1133       }
1134     }
1135
1136 }
1137
1138 int NextIntegerFromString( char ** str, long * value )
1139 {
1140     int result = -1;
1141     char * s = *str;
1142
1143     while( *s == ' ' || *s == '\t' ) {
1144         s++;
1145     }
1146
1147     *value = 0;
1148
1149     if( *s >= '0' && *s <= '9' ) {
1150         while( *s >= '0' && *s <= '9' ) {
1151             *value = *value * 10 + (*s - '0');
1152             s++;
1153         }
1154
1155         result = 0;
1156     }
1157
1158     *str = s;
1159
1160     return result;
1161 }
1162
1163 int NextTimeControlFromString( char ** str, long * value )
1164 {
1165     long temp;
1166     int result = NextIntegerFromString( str, &temp );
1167
1168     if( result == 0 ) {
1169         *value = temp * 60; /* Minutes */
1170         if( **str == ':' ) {
1171             (*str)++;
1172             result = NextIntegerFromString( str, &temp );
1173             *value += temp; /* Seconds */
1174         }
1175     }
1176
1177     return result;
1178 }
1179
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182     int result = -1, type = 0; long temp, temp2;
1183
1184     if(**str != ':') return -1; // old params remain in force!
1185     (*str)++;
1186     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187     if( NextIntegerFromString( str, &temp ) ) return -1;
1188     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189
1190     if(**str != '/') {
1191         /* time only: incremental or sudden-death time control */
1192         if(**str == '+') { /* increment follows; read it */
1193             (*str)++;
1194             if(**str == '!') type = *(*str)++; // Bronstein TC
1195             if(result = NextIntegerFromString( str, &temp2)) return -1;
1196             *inc = temp2 * 1000;
1197             if(**str == '.') { // read fraction of increment
1198                 char *start = ++(*str);
1199                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200                 temp2 *= 1000;
1201                 while(start++ < *str) temp2 /= 10;
1202                 *inc += temp2;
1203             }
1204         } else *inc = 0;
1205         *moves = 0; *tc = temp * 1000; *incType = type;
1206         return 0;
1207     }
1208
1209     (*str)++; /* classical time control */
1210     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211
1212     if(result == 0) {
1213         *moves = temp;
1214         *tc    = temp2 * 1000;
1215         *inc   = 0;
1216         *incType = type;
1217     }
1218     return result;
1219 }
1220
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 {   /* [HGM] get time to add from the multi-session time-control string */
1223     int incType, moves=1; /* kludge to force reading of first session */
1224     long time, increment;
1225     char *s = tcString;
1226
1227     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229     do {
1230         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233         if(movenr == -1) return time;    /* last move before new session     */
1234         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236         if(!moves) return increment;     /* current session is incremental   */
1237         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238     } while(movenr >= -1);               /* try again for next session       */
1239
1240     return 0; // no new time quota on this move
1241 }
1242
1243 int
1244 ParseTimeControl(tc, ti, mps)
1245      char *tc;
1246      float ti;
1247      int mps;
1248 {
1249   long tc1;
1250   long tc2;
1251   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252   int min, sec=0;
1253
1254   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1257   if(ti > 0) {
1258
1259     if(mps)
1260       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261     else 
1262       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263   } else {
1264     if(mps)
1265       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266     else 
1267       snprintf(buf, MSG_SIZ, ":%s", mytc);
1268   }
1269   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270   
1271   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1272     return FALSE;
1273   }
1274
1275   if( *tc == '/' ) {
1276     /* Parse second time control */
1277     tc++;
1278
1279     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1280       return FALSE;
1281     }
1282
1283     if( tc2 == 0 ) {
1284       return FALSE;
1285     }
1286
1287     timeControl_2 = tc2 * 1000;
1288   }
1289   else {
1290     timeControl_2 = 0;
1291   }
1292
1293   if( tc1 == 0 ) {
1294     return FALSE;
1295   }
1296
1297   timeControl = tc1 * 1000;
1298
1299   if (ti >= 0) {
1300     timeIncrement = ti * 1000;  /* convert to ms */
1301     movesPerSession = 0;
1302   } else {
1303     timeIncrement = 0;
1304     movesPerSession = mps;
1305   }
1306   return TRUE;
1307 }
1308
1309 void
1310 InitBackEnd2()
1311 {
1312     if (appData.debugMode) {
1313         fprintf(debugFP, "%s\n", programVersion);
1314     }
1315
1316     set_cont_sequence(appData.wrapContSeq);
1317     if (appData.matchGames > 0) {
1318         appData.matchMode = TRUE;
1319     } else if (appData.matchMode) {
1320         appData.matchGames = 1;
1321     }
1322     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323         appData.matchGames = appData.sameColorGames;
1324     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327     }
1328     Reset(TRUE, FALSE);
1329     if (appData.noChessProgram || first.protocolVersion == 1) {
1330       InitBackEnd3();
1331     } else {
1332       /* kludge: allow timeout for initial "feature" commands */
1333       FreezeUI();
1334       DisplayMessage("", _("Starting chess program"));
1335       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1336     }
1337 }
1338
1339 int
1340 CalculateIndex(int index, int gameNr)
1341 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342     int res;
1343     if(index > 0) return index; // fixed nmber
1344     if(index == 0) return 1;
1345     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1347     return res;
1348 }
1349
1350 int
1351 LoadGameOrPosition(int gameNr)
1352 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353     if (*appData.loadGameFile != NULLCHAR) {
1354         if (!LoadGameFromFile(appData.loadGameFile,
1355                 CalculateIndex(appData.loadGameIndex, gameNr),
1356                               appData.loadGameFile, FALSE)) {
1357             DisplayFatalError(_("Bad game file"), 0, 1);
1358             return 0;
1359         }
1360     } else if (*appData.loadPositionFile != NULLCHAR) {
1361         if (!LoadPositionFromFile(appData.loadPositionFile,
1362                 CalculateIndex(appData.loadPositionIndex, gameNr),
1363                                   appData.loadPositionFile)) {
1364             DisplayFatalError(_("Bad position file"), 0, 1);
1365             return 0;
1366         }
1367     }
1368     return 1;
1369 }
1370
1371 void
1372 ReserveGame(int gameNr, char resChar)
1373 {
1374     FILE *tf = fopen(appData.tourneyFile, "r+");
1375     char *p, *q, c, buf[MSG_SIZ];
1376     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377     safeStrCpy(buf, lastMsg, MSG_SIZ);
1378     DisplayMessage(_("Pick new game"), "");
1379     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380     ParseArgsFromFile(tf);
1381     p = q = appData.results;
1382     if(appData.debugMode) {
1383       char *r = appData.participants;
1384       fprintf(debugFP, "results = '%s'\n", p);
1385       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386       fprintf(debugFP, "\n");
1387     }
1388     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389     nextGame = q - p;
1390     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391     safeStrCpy(q, p, strlen(p) + 2);
1392     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396         q[nextGame] = '*';
1397     }
1398     fseek(tf, -(strlen(p)+4), SEEK_END);
1399     c = fgetc(tf);
1400     if(c != '"') // depending on DOS or Unix line endings we can be one off
1401          fseek(tf, -(strlen(p)+2), SEEK_END);
1402     else fseek(tf, -(strlen(p)+3), SEEK_END);
1403     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404     DisplayMessage(buf, "");
1405     free(p); appData.results = q;
1406     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408         UnloadEngine(&first);  // next game belongs to other pairing;
1409         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1410     }
1411 }
1412
1413 void
1414 MatchEvent(int mode)
1415 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416         int dummy;
1417         if(matchMode) { // already in match mode: switch it off
1418             abortMatch = TRUE;
1419             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420             return;
1421         }
1422 //      if(gameMode != BeginningOfGame) {
1423 //          DisplayError(_("You can only start a match from the initial position."), 0);
1424 //          return;
1425 //      }
1426         abortMatch = FALSE;
1427         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428         /* Set up machine vs. machine match */
1429         nextGame = 0;
1430         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431         if(appData.tourneyFile[0]) {
1432             ReserveGame(-1, 0);
1433             if(nextGame > appData.matchGames) {
1434                 char buf[MSG_SIZ];
1435                 if(strchr(appData.results, '*') == NULL) {
1436                     FILE *f;
1437                     appData.tourneyCycles++;
1438                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1439                         fclose(f);
1440                         NextTourneyGame(-1, &dummy);
1441                         ReserveGame(-1, 0);
1442                         if(nextGame <= appData.matchGames) {
1443                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444                             matchMode = mode;
1445                             ScheduleDelayedEvent(NextMatchGame, 10000);
1446                             return;
1447                         }
1448                     }
1449                 }
1450                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451                 DisplayError(buf, 0);
1452                 appData.tourneyFile[0] = 0;
1453                 return;
1454             }
1455         } else
1456         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1457             DisplayFatalError(_("Can't have a match with no chess programs"),
1458                               0, 2);
1459             return;
1460         }
1461         matchMode = mode;
1462         matchGame = roundNr = 1;
1463         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1464         NextMatchGame();
1465 }
1466
1467 void
1468 InitBackEnd3 P((void))
1469 {
1470     GameMode initialMode;
1471     char buf[MSG_SIZ];
1472     int err, len;
1473
1474     InitChessProgram(&first, startedFromSetupPosition);
1475
1476     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1477         free(programVersion);
1478         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480     }
1481
1482     if (appData.icsActive) {
1483 #ifdef WIN32
1484         /* [DM] Make a console window if needed [HGM] merged ifs */
1485         ConsoleCreate();
1486 #endif
1487         err = establish();
1488         if (err != 0)
1489           {
1490             if (*appData.icsCommPort != NULLCHAR)
1491               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492                              appData.icsCommPort);
1493             else
1494               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495                         appData.icsHost, appData.icsPort);
1496
1497             if( (len > MSG_SIZ) && appData.debugMode )
1498               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499
1500             DisplayFatalError(buf, err, 1);
1501             return;
1502         }
1503         SetICSMode();
1504         telnetISR =
1505           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506         fromUserISR =
1507           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510     } else if (appData.noChessProgram) {
1511         SetNCPMode();
1512     } else {
1513         SetGNUMode();
1514     }
1515
1516     if (*appData.cmailGameName != NULLCHAR) {
1517         SetCmailMode();
1518         OpenLoopback(&cmailPR);
1519         cmailISR =
1520           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1521     }
1522
1523     ThawUI();
1524     DisplayMessage("", "");
1525     if (StrCaseCmp(appData.initialMode, "") == 0) {
1526       initialMode = BeginningOfGame;
1527       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531         ModeHighlight();
1532       }
1533     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534       initialMode = TwoMachinesPlay;
1535     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536       initialMode = AnalyzeFile;
1537     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538       initialMode = AnalyzeMode;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540       initialMode = MachinePlaysWhite;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542       initialMode = MachinePlaysBlack;
1543     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544       initialMode = EditGame;
1545     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546       initialMode = EditPosition;
1547     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548       initialMode = Training;
1549     } else {
1550       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551       if( (len > MSG_SIZ) && appData.debugMode )
1552         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553
1554       DisplayFatalError(buf, 0, 2);
1555       return;
1556     }
1557
1558     if (appData.matchMode) {
1559         if(appData.tourneyFile[0]) { // start tourney from command line
1560             FILE *f;
1561             if(f = fopen(appData.tourneyFile, "r")) {
1562                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563                 fclose(f);
1564                 appData.clockMode = TRUE;
1565                 SetGNUMode();
1566             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567         }
1568         MatchEvent(TRUE);
1569     } else if (*appData.cmailGameName != NULLCHAR) {
1570         /* Set up cmail mode */
1571         ReloadCmailMsgEvent(TRUE);
1572     } else {
1573         /* Set up other modes */
1574         if (initialMode == AnalyzeFile) {
1575           if (*appData.loadGameFile == NULLCHAR) {
1576             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1577             return;
1578           }
1579         }
1580         if (*appData.loadGameFile != NULLCHAR) {
1581             (void) LoadGameFromFile(appData.loadGameFile,
1582                                     appData.loadGameIndex,
1583                                     appData.loadGameFile, TRUE);
1584         } else if (*appData.loadPositionFile != NULLCHAR) {
1585             (void) LoadPositionFromFile(appData.loadPositionFile,
1586                                         appData.loadPositionIndex,
1587                                         appData.loadPositionFile);
1588             /* [HGM] try to make self-starting even after FEN load */
1589             /* to allow automatic setup of fairy variants with wtm */
1590             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591                 gameMode = BeginningOfGame;
1592                 setboardSpoiledMachineBlack = 1;
1593             }
1594             /* [HGM] loadPos: make that every new game uses the setup */
1595             /* from file as long as we do not switch variant          */
1596             if(!blackPlaysFirst) {
1597                 startedFromPositionFile = TRUE;
1598                 CopyBoard(filePosition, boards[0]);
1599             }
1600         }
1601         if (initialMode == AnalyzeMode) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1608             return;
1609           }
1610           AnalyzeModeEvent();
1611         } else if (initialMode == AnalyzeFile) {
1612           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613           ShowThinkingEvent();
1614           AnalyzeFileEvent();
1615           AnalysisPeriodicEvent(1);
1616         } else if (initialMode == MachinePlaysWhite) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineWhiteEvent();
1628         } else if (initialMode == MachinePlaysBlack) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           MachineBlackEvent();
1640         } else if (initialMode == TwoMachinesPlay) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           TwoMachinesEvent();
1652         } else if (initialMode == EditGame) {
1653           EditGameEvent();
1654         } else if (initialMode == EditPosition) {
1655           EditPositionEvent();
1656         } else if (initialMode == Training) {
1657           if (*appData.loadGameFile == NULLCHAR) {
1658             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659             return;
1660           }
1661           TrainingEvent();
1662         }
1663     }
1664 }
1665
1666 void
1667 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1668 {
1669     DisplayBook(current+1);
1670
1671     MoveHistorySet( movelist, first, last, current, pvInfoList );
1672
1673     EvalGraphSet( first, last, current, pvInfoList );
1674
1675     MakeEngineOutputTitle();
1676 }
1677
1678 /*
1679  * Establish will establish a contact to a remote host.port.
1680  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1681  *  used to talk to the host.
1682  * Returns 0 if okay, error code if not.
1683  */
1684 int
1685 establish()
1686 {
1687     char buf[MSG_SIZ];
1688
1689     if (*appData.icsCommPort != NULLCHAR) {
1690         /* Talk to the host through a serial comm port */
1691         return OpenCommPort(appData.icsCommPort, &icsPR);
1692
1693     } else if (*appData.gateway != NULLCHAR) {
1694         if (*appData.remoteShell == NULLCHAR) {
1695             /* Use the rcmd protocol to run telnet program on a gateway host */
1696             snprintf(buf, sizeof(buf), "%s %s %s",
1697                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1698             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1699
1700         } else {
1701             /* Use the rsh program to run telnet program on a gateway host */
1702             if (*appData.remoteUser == NULLCHAR) {
1703                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1704                         appData.gateway, appData.telnetProgram,
1705                         appData.icsHost, appData.icsPort);
1706             } else {
1707                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1708                         appData.remoteShell, appData.gateway,
1709                         appData.remoteUser, appData.telnetProgram,
1710                         appData.icsHost, appData.icsPort);
1711             }
1712             return StartChildProcess(buf, "", &icsPR);
1713
1714         }
1715     } else if (appData.useTelnet) {
1716         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1717
1718     } else {
1719         /* TCP socket interface differs somewhat between
1720            Unix and NT; handle details in the front end.
1721            */
1722         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1723     }
1724 }
1725
1726 void EscapeExpand(char *p, char *q)
1727 {       // [HGM] initstring: routine to shape up string arguments
1728         while(*p++ = *q++) if(p[-1] == '\\')
1729             switch(*q++) {
1730                 case 'n': p[-1] = '\n'; break;
1731                 case 'r': p[-1] = '\r'; break;
1732                 case 't': p[-1] = '\t'; break;
1733                 case '\\': p[-1] = '\\'; break;
1734                 case 0: *p = 0; return;
1735                 default: p[-1] = q[-1]; break;
1736             }
1737 }
1738
1739 void
1740 show_bytes(fp, buf, count)
1741      FILE *fp;
1742      char *buf;
1743      int count;
1744 {
1745     while (count--) {
1746         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1747             fprintf(fp, "\\%03o", *buf & 0xff);
1748         } else {
1749             putc(*buf, fp);
1750         }
1751         buf++;
1752     }
1753     fflush(fp);
1754 }
1755
1756 /* Returns an errno value */
1757 int
1758 OutputMaybeTelnet(pr, message, count, outError)
1759      ProcRef pr;
1760      char *message;
1761      int count;
1762      int *outError;
1763 {
1764     char buf[8192], *p, *q, *buflim;
1765     int left, newcount, outcount;
1766
1767     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1768         *appData.gateway != NULLCHAR) {
1769         if (appData.debugMode) {
1770             fprintf(debugFP, ">ICS: ");
1771             show_bytes(debugFP, message, count);
1772             fprintf(debugFP, "\n");
1773         }
1774         return OutputToProcess(pr, message, count, outError);
1775     }
1776
1777     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1778     p = message;
1779     q = buf;
1780     left = count;
1781     newcount = 0;
1782     while (left) {
1783         if (q >= buflim) {
1784             if (appData.debugMode) {
1785                 fprintf(debugFP, ">ICS: ");
1786                 show_bytes(debugFP, buf, newcount);
1787                 fprintf(debugFP, "\n");
1788             }
1789             outcount = OutputToProcess(pr, buf, newcount, outError);
1790             if (outcount < newcount) return -1; /* to be sure */
1791             q = buf;
1792             newcount = 0;
1793         }
1794         if (*p == '\n') {
1795             *q++ = '\r';
1796             newcount++;
1797         } else if (((unsigned char) *p) == TN_IAC) {
1798             *q++ = (char) TN_IAC;
1799             newcount ++;
1800         }
1801         *q++ = *p++;
1802         newcount++;
1803         left--;
1804     }
1805     if (appData.debugMode) {
1806         fprintf(debugFP, ">ICS: ");
1807         show_bytes(debugFP, buf, newcount);
1808         fprintf(debugFP, "\n");
1809     }
1810     outcount = OutputToProcess(pr, buf, newcount, outError);
1811     if (outcount < newcount) return -1; /* to be sure */
1812     return count;
1813 }
1814
1815 void
1816 read_from_player(isr, closure, message, count, error)
1817      InputSourceRef isr;
1818      VOIDSTAR closure;
1819      char *message;
1820      int count;
1821      int error;
1822 {
1823     int outError, outCount;
1824     static int gotEof = 0;
1825
1826     /* Pass data read from player on to ICS */
1827     if (count > 0) {
1828         gotEof = 0;
1829         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1830         if (outCount < count) {
1831             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1832         }
1833     } else if (count < 0) {
1834         RemoveInputSource(isr);
1835         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1836     } else if (gotEof++ > 0) {
1837         RemoveInputSource(isr);
1838         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1839     }
1840 }
1841
1842 void
1843 KeepAlive()
1844 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1845     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1846     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1847     SendToICS("date\n");
1848     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1849 }
1850
1851 /* added routine for printf style output to ics */
1852 void ics_printf(char *format, ...)
1853 {
1854     char buffer[MSG_SIZ];
1855     va_list args;
1856
1857     va_start(args, format);
1858     vsnprintf(buffer, sizeof(buffer), format, args);
1859     buffer[sizeof(buffer)-1] = '\0';
1860     SendToICS(buffer);
1861     va_end(args);
1862 }
1863
1864 void
1865 SendToICS(s)
1866      char *s;
1867 {
1868     int count, outCount, outError;
1869
1870     if (icsPR == NULL) return;
1871
1872     count = strlen(s);
1873     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1874     if (outCount < count) {
1875         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1876     }
1877 }
1878
1879 /* This is used for sending logon scripts to the ICS. Sending
1880    without a delay causes problems when using timestamp on ICC
1881    (at least on my machine). */
1882 void
1883 SendToICSDelayed(s,msdelay)
1884      char *s;
1885      long msdelay;
1886 {
1887     int count, outCount, outError;
1888
1889     if (icsPR == NULL) return;
1890
1891     count = strlen(s);
1892     if (appData.debugMode) {
1893         fprintf(debugFP, ">ICS: ");
1894         show_bytes(debugFP, s, count);
1895         fprintf(debugFP, "\n");
1896     }
1897     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1898                                       msdelay);
1899     if (outCount < count) {
1900         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901     }
1902 }
1903
1904
1905 /* Remove all highlighting escape sequences in s
1906    Also deletes any suffix starting with '('
1907    */
1908 char *
1909 StripHighlightAndTitle(s)
1910      char *s;
1911 {
1912     static char retbuf[MSG_SIZ];
1913     char *p = retbuf;
1914
1915     while (*s != NULLCHAR) {
1916         while (*s == '\033') {
1917             while (*s != NULLCHAR && !isalpha(*s)) s++;
1918             if (*s != NULLCHAR) s++;
1919         }
1920         while (*s != NULLCHAR && *s != '\033') {
1921             if (*s == '(' || *s == '[') {
1922                 *p = NULLCHAR;
1923                 return retbuf;
1924             }
1925             *p++ = *s++;
1926         }
1927     }
1928     *p = NULLCHAR;
1929     return retbuf;
1930 }
1931
1932 /* Remove all highlighting escape sequences in s */
1933 char *
1934 StripHighlight(s)
1935      char *s;
1936 {
1937     static char retbuf[MSG_SIZ];
1938     char *p = retbuf;
1939
1940     while (*s != NULLCHAR) {
1941         while (*s == '\033') {
1942             while (*s != NULLCHAR && !isalpha(*s)) s++;
1943             if (*s != NULLCHAR) s++;
1944         }
1945         while (*s != NULLCHAR && *s != '\033') {
1946             *p++ = *s++;
1947         }
1948     }
1949     *p = NULLCHAR;
1950     return retbuf;
1951 }
1952
1953 char *variantNames[] = VARIANT_NAMES;
1954 char *
1955 VariantName(v)
1956      VariantClass v;
1957 {
1958     return variantNames[v];
1959 }
1960
1961
1962 /* Identify a variant from the strings the chess servers use or the
1963    PGN Variant tag names we use. */
1964 VariantClass
1965 StringToVariant(e)
1966      char *e;
1967 {
1968     char *p;
1969     int wnum = -1;
1970     VariantClass v = VariantNormal;
1971     int i, found = FALSE;
1972     char buf[MSG_SIZ];
1973     int len;
1974
1975     if (!e) return v;
1976
1977     /* [HGM] skip over optional board-size prefixes */
1978     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1979         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1980         while( *e++ != '_');
1981     }
1982
1983     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1984         v = VariantNormal;
1985         found = TRUE;
1986     } else
1987     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1988       if (StrCaseStr(e, variantNames[i])) {
1989         v = (VariantClass) i;
1990         found = TRUE;
1991         break;
1992       }
1993     }
1994
1995     if (!found) {
1996       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1997           || StrCaseStr(e, "wild/fr")
1998           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1999         v = VariantFischeRandom;
2000       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2001                  (i = 1, p = StrCaseStr(e, "w"))) {
2002         p += i;
2003         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2004         if (isdigit(*p)) {
2005           wnum = atoi(p);
2006         } else {
2007           wnum = -1;
2008         }
2009         switch (wnum) {
2010         case 0: /* FICS only, actually */
2011         case 1:
2012           /* Castling legal even if K starts on d-file */
2013           v = VariantWildCastle;
2014           break;
2015         case 2:
2016         case 3:
2017         case 4:
2018           /* Castling illegal even if K & R happen to start in
2019              normal positions. */
2020           v = VariantNoCastle;
2021           break;
2022         case 5:
2023         case 7:
2024         case 8:
2025         case 10:
2026         case 11:
2027         case 12:
2028         case 13:
2029         case 14:
2030         case 15:
2031         case 18:
2032         case 19:
2033           /* Castling legal iff K & R start in normal positions */
2034           v = VariantNormal;
2035           break;
2036         case 6:
2037         case 20:
2038         case 21:
2039           /* Special wilds for position setup; unclear what to do here */
2040           v = VariantLoadable;
2041           break;
2042         case 9:
2043           /* Bizarre ICC game */
2044           v = VariantTwoKings;
2045           break;
2046         case 16:
2047           v = VariantKriegspiel;
2048           break;
2049         case 17:
2050           v = VariantLosers;
2051           break;
2052         case 22:
2053           v = VariantFischeRandom;
2054           break;
2055         case 23:
2056           v = VariantCrazyhouse;
2057           break;
2058         case 24:
2059           v = VariantBughouse;
2060           break;
2061         case 25:
2062           v = Variant3Check;
2063           break;
2064         case 26:
2065           /* Not quite the same as FICS suicide! */
2066           v = VariantGiveaway;
2067           break;
2068         case 27:
2069           v = VariantAtomic;
2070           break;
2071         case 28:
2072           v = VariantShatranj;
2073           break;
2074
2075         /* Temporary names for future ICC types.  The name *will* change in
2076            the next xboard/WinBoard release after ICC defines it. */
2077         case 29:
2078           v = Variant29;
2079           break;
2080         case 30:
2081           v = Variant30;
2082           break;
2083         case 31:
2084           v = Variant31;
2085           break;
2086         case 32:
2087           v = Variant32;
2088           break;
2089         case 33:
2090           v = Variant33;
2091           break;
2092         case 34:
2093           v = Variant34;
2094           break;
2095         case 35:
2096           v = Variant35;
2097           break;
2098         case 36:
2099           v = Variant36;
2100           break;
2101         case 37:
2102           v = VariantShogi;
2103           break;
2104         case 38:
2105           v = VariantXiangqi;
2106           break;
2107         case 39:
2108           v = VariantCourier;
2109           break;
2110         case 40:
2111           v = VariantGothic;
2112           break;
2113         case 41:
2114           v = VariantCapablanca;
2115           break;
2116         case 42:
2117           v = VariantKnightmate;
2118           break;
2119         case 43:
2120           v = VariantFairy;
2121           break;
2122         case 44:
2123           v = VariantCylinder;
2124           break;
2125         case 45:
2126           v = VariantFalcon;
2127           break;
2128         case 46:
2129           v = VariantCapaRandom;
2130           break;
2131         case 47:
2132           v = VariantBerolina;
2133           break;
2134         case 48:
2135           v = VariantJanus;
2136           break;
2137         case 49:
2138           v = VariantSuper;
2139           break;
2140         case 50:
2141           v = VariantGreat;
2142           break;
2143         case -1:
2144           /* Found "wild" or "w" in the string but no number;
2145              must assume it's normal chess. */
2146           v = VariantNormal;
2147           break;
2148         default:
2149           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2150           if( (len > MSG_SIZ) && appData.debugMode )
2151             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2152
2153           DisplayError(buf, 0);
2154           v = VariantUnknown;
2155           break;
2156         }
2157       }
2158     }
2159     if (appData.debugMode) {
2160       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2161               e, wnum, VariantName(v));
2162     }
2163     return v;
2164 }
2165
2166 static int leftover_start = 0, leftover_len = 0;
2167 char star_match[STAR_MATCH_N][MSG_SIZ];
2168
2169 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2170    advance *index beyond it, and set leftover_start to the new value of
2171    *index; else return FALSE.  If pattern contains the character '*', it
2172    matches any sequence of characters not containing '\r', '\n', or the
2173    character following the '*' (if any), and the matched sequence(s) are
2174    copied into star_match.
2175    */
2176 int
2177 looking_at(buf, index, pattern)
2178      char *buf;
2179      int *index;
2180      char *pattern;
2181 {
2182     char *bufp = &buf[*index], *patternp = pattern;
2183     int star_count = 0;
2184     char *matchp = star_match[0];
2185
2186     for (;;) {
2187         if (*patternp == NULLCHAR) {
2188             *index = leftover_start = bufp - buf;
2189             *matchp = NULLCHAR;
2190             return TRUE;
2191         }
2192         if (*bufp == NULLCHAR) return FALSE;
2193         if (*patternp == '*') {
2194             if (*bufp == *(patternp + 1)) {
2195                 *matchp = NULLCHAR;
2196                 matchp = star_match[++star_count];
2197                 patternp += 2;
2198                 bufp++;
2199                 continue;
2200             } else if (*bufp == '\n' || *bufp == '\r') {
2201                 patternp++;
2202                 if (*patternp == NULLCHAR)
2203                   continue;
2204                 else
2205                   return FALSE;
2206             } else {
2207                 *matchp++ = *bufp++;
2208                 continue;
2209             }
2210         }
2211         if (*patternp != *bufp) return FALSE;
2212         patternp++;
2213         bufp++;
2214     }
2215 }
2216
2217 void
2218 SendToPlayer(data, length)
2219      char *data;
2220      int length;
2221 {
2222     int error, outCount;
2223     outCount = OutputToProcess(NoProc, data, length, &error);
2224     if (outCount < length) {
2225         DisplayFatalError(_("Error writing to display"), error, 1);
2226     }
2227 }
2228
2229 void
2230 PackHolding(packed, holding)
2231      char packed[];
2232      char *holding;
2233 {
2234     char *p = holding;
2235     char *q = packed;
2236     int runlength = 0;
2237     int curr = 9999;
2238     do {
2239         if (*p == curr) {
2240             runlength++;
2241         } else {
2242             switch (runlength) {
2243               case 0:
2244                 break;
2245               case 1:
2246                 *q++ = curr;
2247                 break;
2248               case 2:
2249                 *q++ = curr;
2250                 *q++ = curr;
2251                 break;
2252               default:
2253                 sprintf(q, "%d", runlength);
2254                 while (*q) q++;
2255                 *q++ = curr;
2256                 break;
2257             }
2258             runlength = 1;
2259             curr = *p;
2260         }
2261     } while (*p++);
2262     *q = NULLCHAR;
2263 }
2264
2265 /* Telnet protocol requests from the front end */
2266 void
2267 TelnetRequest(ddww, option)
2268      unsigned char ddww, option;
2269 {
2270     unsigned char msg[3];
2271     int outCount, outError;
2272
2273     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2274
2275     if (appData.debugMode) {
2276         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2277         switch (ddww) {
2278           case TN_DO:
2279             ddwwStr = "DO";
2280             break;
2281           case TN_DONT:
2282             ddwwStr = "DONT";
2283             break;
2284           case TN_WILL:
2285             ddwwStr = "WILL";
2286             break;
2287           case TN_WONT:
2288             ddwwStr = "WONT";
2289             break;
2290           default:
2291             ddwwStr = buf1;
2292             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2293             break;
2294         }
2295         switch (option) {
2296           case TN_ECHO:
2297             optionStr = "ECHO";
2298             break;
2299           default:
2300             optionStr = buf2;
2301             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2302             break;
2303         }
2304         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2305     }
2306     msg[0] = TN_IAC;
2307     msg[1] = ddww;
2308     msg[2] = option;
2309     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2310     if (outCount < 3) {
2311         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2312     }
2313 }
2314
2315 void
2316 DoEcho()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DO, TN_ECHO);
2320 }
2321
2322 void
2323 DontEcho()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DONT, TN_ECHO);
2327 }
2328
2329 void
2330 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2331 {
2332     /* put the holdings sent to us by the server on the board holdings area */
2333     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2334     char p;
2335     ChessSquare piece;
2336
2337     if(gameInfo.holdingsWidth < 2)  return;
2338     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2339         return; // prevent overwriting by pre-board holdings
2340
2341     if( (int)lowestPiece >= BlackPawn ) {
2342         holdingsColumn = 0;
2343         countsColumn = 1;
2344         holdingsStartRow = BOARD_HEIGHT-1;
2345         direction = -1;
2346     } else {
2347         holdingsColumn = BOARD_WIDTH-1;
2348         countsColumn = BOARD_WIDTH-2;
2349         holdingsStartRow = 0;
2350         direction = 1;
2351     }
2352
2353     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2354         board[i][holdingsColumn] = EmptySquare;
2355         board[i][countsColumn]   = (ChessSquare) 0;
2356     }
2357     while( (p=*holdings++) != NULLCHAR ) {
2358         piece = CharToPiece( ToUpper(p) );
2359         if(piece == EmptySquare) continue;
2360         /*j = (int) piece - (int) WhitePawn;*/
2361         j = PieceToNumber(piece);
2362         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2363         if(j < 0) continue;               /* should not happen */
2364         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2365         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2366         board[holdingsStartRow+j*direction][countsColumn]++;
2367     }
2368 }
2369
2370
2371 void
2372 VariantSwitch(Board board, VariantClass newVariant)
2373 {
2374    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2375    static Board oldBoard;
2376
2377    startedFromPositionFile = FALSE;
2378    if(gameInfo.variant == newVariant) return;
2379
2380    /* [HGM] This routine is called each time an assignment is made to
2381     * gameInfo.variant during a game, to make sure the board sizes
2382     * are set to match the new variant. If that means adding or deleting
2383     * holdings, we shift the playing board accordingly
2384     * This kludge is needed because in ICS observe mode, we get boards
2385     * of an ongoing game without knowing the variant, and learn about the
2386     * latter only later. This can be because of the move list we requested,
2387     * in which case the game history is refilled from the beginning anyway,
2388     * but also when receiving holdings of a crazyhouse game. In the latter
2389     * case we want to add those holdings to the already received position.
2390     */
2391
2392
2393    if (appData.debugMode) {
2394      fprintf(debugFP, "Switch board from %s to %s\n",
2395              VariantName(gameInfo.variant), VariantName(newVariant));
2396      setbuf(debugFP, NULL);
2397    }
2398    shuffleOpenings = 0;       /* [HGM] shuffle */
2399    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2400    switch(newVariant)
2401      {
2402      case VariantShogi:
2403        newWidth = 9;  newHeight = 9;
2404        gameInfo.holdingsSize = 7;
2405      case VariantBughouse:
2406      case VariantCrazyhouse:
2407        newHoldingsWidth = 2; break;
2408      case VariantGreat:
2409        newWidth = 10;
2410      case VariantSuper:
2411        newHoldingsWidth = 2;
2412        gameInfo.holdingsSize = 8;
2413        break;
2414      case VariantGothic:
2415      case VariantCapablanca:
2416      case VariantCapaRandom:
2417        newWidth = 10;
2418      default:
2419        newHoldingsWidth = gameInfo.holdingsSize = 0;
2420      };
2421
2422    if(newWidth  != gameInfo.boardWidth  ||
2423       newHeight != gameInfo.boardHeight ||
2424       newHoldingsWidth != gameInfo.holdingsWidth ) {
2425
2426      /* shift position to new playing area, if needed */
2427      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432        for(i=0; i<newHeight; i++) {
2433          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2434          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2435        }
2436      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2437        for(i=0; i<BOARD_HEIGHT; i++)
2438          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2439            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440              board[i][j];
2441      }
2442      gameInfo.boardWidth  = newWidth;
2443      gameInfo.boardHeight = newHeight;
2444      gameInfo.holdingsWidth = newHoldingsWidth;
2445      gameInfo.variant = newVariant;
2446      InitDrawingSizes(-2, 0);
2447    } else gameInfo.variant = newVariant;
2448    CopyBoard(oldBoard, board);   // remember correctly formatted board
2449      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2450    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2451 }
2452
2453 static int loggedOn = FALSE;
2454
2455 /*-- Game start info cache: --*/
2456 int gs_gamenum;
2457 char gs_kind[MSG_SIZ];
2458 static char player1Name[128] = "";
2459 static char player2Name[128] = "";
2460 static char cont_seq[] = "\n\\   ";
2461 static int player1Rating = -1;
2462 static int player2Rating = -1;
2463 /*----------------------------*/
2464
2465 ColorClass curColor = ColorNormal;
2466 int suppressKibitz = 0;
2467
2468 // [HGM] seekgraph
2469 Boolean soughtPending = FALSE;
2470 Boolean seekGraphUp;
2471 #define MAX_SEEK_ADS 200
2472 #define SQUARE 0x80
2473 char *seekAdList[MAX_SEEK_ADS];
2474 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2475 float tcList[MAX_SEEK_ADS];
2476 char colorList[MAX_SEEK_ADS];
2477 int nrOfSeekAds = 0;
2478 int minRating = 1010, maxRating = 2800;
2479 int hMargin = 10, vMargin = 20, h, w;
2480 extern int squareSize, lineGap;
2481
2482 void
2483 PlotSeekAd(int i)
2484 {
2485         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2486         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2487         if(r < minRating+100 && r >=0 ) r = minRating+100;
2488         if(r > maxRating) r = maxRating;
2489         if(tc < 1.) tc = 1.;
2490         if(tc > 95.) tc = 95.;
2491         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2492         y = ((double)r - minRating)/(maxRating - minRating)
2493             * (h-vMargin-squareSize/8-1) + vMargin;
2494         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2495         if(strstr(seekAdList[i], " u ")) color = 1;
2496         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2497            !strstr(seekAdList[i], "bullet") &&
2498            !strstr(seekAdList[i], "blitz") &&
2499            !strstr(seekAdList[i], "standard") ) color = 2;
2500         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2501         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2502 }
2503
2504 void
2505 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2506 {
2507         char buf[MSG_SIZ], *ext = "";
2508         VariantClass v = StringToVariant(type);
2509         if(strstr(type, "wild")) {
2510             ext = type + 4; // append wild number
2511             if(v == VariantFischeRandom) type = "chess960"; else
2512             if(v == VariantLoadable) type = "setup"; else
2513             type = VariantName(v);
2514         }
2515         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2516         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2517             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2518             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2519             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2520             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2521             seekNrList[nrOfSeekAds] = nr;
2522             zList[nrOfSeekAds] = 0;
2523             seekAdList[nrOfSeekAds++] = StrSave(buf);
2524             if(plot) PlotSeekAd(nrOfSeekAds-1);
2525         }
2526 }
2527
2528 void
2529 EraseSeekDot(int i)
2530 {
2531     int x = xList[i], y = yList[i], d=squareSize/4, k;
2532     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2533     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2534     // now replot every dot that overlapped
2535     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2536         int xx = xList[k], yy = yList[k];
2537         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2538             DrawSeekDot(xx, yy, colorList[k]);
2539     }
2540 }
2541
2542 void
2543 RemoveSeekAd(int nr)
2544 {
2545         int i;
2546         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2547             EraseSeekDot(i);
2548             if(seekAdList[i]) free(seekAdList[i]);
2549             seekAdList[i] = seekAdList[--nrOfSeekAds];
2550             seekNrList[i] = seekNrList[nrOfSeekAds];
2551             ratingList[i] = ratingList[nrOfSeekAds];
2552             colorList[i]  = colorList[nrOfSeekAds];
2553             tcList[i] = tcList[nrOfSeekAds];
2554             xList[i]  = xList[nrOfSeekAds];
2555             yList[i]  = yList[nrOfSeekAds];
2556             zList[i]  = zList[nrOfSeekAds];
2557             seekAdList[nrOfSeekAds] = NULL;
2558             break;
2559         }
2560 }
2561
2562 Boolean
2563 MatchSoughtLine(char *line)
2564 {
2565     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2566     int nr, base, inc, u=0; char dummy;
2567
2568     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2570        (u=1) &&
2571        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2573         // match: compact and save the line
2574         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2575         return TRUE;
2576     }
2577     return FALSE;
2578 }
2579
2580 int
2581 DrawSeekGraph()
2582 {
2583     int i;
2584     if(!seekGraphUp) return FALSE;
2585     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2586     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2587
2588     DrawSeekBackground(0, 0, w, h);
2589     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2590     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2591     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2592         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2593         yy = h-1-yy;
2594         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2595         if(i%500 == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2599         }
2600     }
2601     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2602     for(i=1; i<100; i+=(i<10?1:5)) {
2603         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2604         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2605         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2606             char buf[MSG_SIZ];
2607             snprintf(buf, MSG_SIZ, "%d", i);
2608             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2609         }
2610     }
2611     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2612     return TRUE;
2613 }
2614
2615 int SeekGraphClick(ClickType click, int x, int y, int moving)
2616 {
2617     static int lastDown = 0, displayed = 0, lastSecond;
2618     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2619         if(click == Release || moving) return FALSE;
2620         nrOfSeekAds = 0;
2621         soughtPending = TRUE;
2622         SendToICS(ics_prefix);
2623         SendToICS("sought\n"); // should this be "sought all"?
2624     } else { // issue challenge based on clicked ad
2625         int dist = 10000; int i, closest = 0, second = 0;
2626         for(i=0; i<nrOfSeekAds; i++) {
2627             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2628             if(d < dist) { dist = d; closest = i; }
2629             second += (d - zList[i] < 120); // count in-range ads
2630             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2631         }
2632         if(dist < 120) {
2633             char buf[MSG_SIZ];
2634             second = (second > 1);
2635             if(displayed != closest || second != lastSecond) {
2636                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2637                 lastSecond = second; displayed = closest;
2638             }
2639             if(click == Press) {
2640                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2641                 lastDown = closest;
2642                 return TRUE;
2643             } // on press 'hit', only show info
2644             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2645             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2646             SendToICS(ics_prefix);
2647             SendToICS(buf);
2648             return TRUE; // let incoming board of started game pop down the graph
2649         } else if(click == Release) { // release 'miss' is ignored
2650             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2651             if(moving == 2) { // right up-click
2652                 nrOfSeekAds = 0; // refresh graph
2653                 soughtPending = TRUE;
2654                 SendToICS(ics_prefix);
2655                 SendToICS("sought\n"); // should this be "sought all"?
2656             }
2657             return TRUE;
2658         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2659         // press miss or release hit 'pop down' seek graph
2660         seekGraphUp = FALSE;
2661         DrawPosition(TRUE, NULL);
2662     }
2663     return TRUE;
2664 }
2665
2666 void
2667 read_from_ics(isr, closure, data, count, error)
2668      InputSourceRef isr;
2669      VOIDSTAR closure;
2670      char *data;
2671      int count;
2672      int error;
2673 {
2674 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2675 #define STARTED_NONE 0
2676 #define STARTED_MOVES 1
2677 #define STARTED_BOARD 2
2678 #define STARTED_OBSERVE 3
2679 #define STARTED_HOLDINGS 4
2680 #define STARTED_CHATTER 5
2681 #define STARTED_COMMENT 6
2682 #define STARTED_MOVES_NOHIDE 7
2683
2684     static int started = STARTED_NONE;
2685     static char parse[20000];
2686     static int parse_pos = 0;
2687     static char buf[BUF_SIZE + 1];
2688     static int firstTime = TRUE, intfSet = FALSE;
2689     static ColorClass prevColor = ColorNormal;
2690     static int savingComment = FALSE;
2691     static int cmatch = 0; // continuation sequence match
2692     char *bp;
2693     char str[MSG_SIZ];
2694     int i, oldi;
2695     int buf_len;
2696     int next_out;
2697     int tkind;
2698     int backup;    /* [DM] For zippy color lines */
2699     char *p;
2700     char talker[MSG_SIZ]; // [HGM] chat
2701     int channel;
2702
2703     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2704
2705     if (appData.debugMode) {
2706       if (!error) {
2707         fprintf(debugFP, "<ICS: ");
2708         show_bytes(debugFP, data, count);
2709         fprintf(debugFP, "\n");
2710       }
2711     }
2712
2713     if (appData.debugMode) { int f = forwardMostMove;
2714         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2715                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2716                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2717     }
2718     if (count > 0) {
2719         /* If last read ended with a partial line that we couldn't parse,
2720            prepend it to the new read and try again. */
2721         if (leftover_len > 0) {
2722             for (i=0; i<leftover_len; i++)
2723               buf[i] = buf[leftover_start + i];
2724         }
2725
2726     /* copy new characters into the buffer */
2727     bp = buf + leftover_len;
2728     buf_len=leftover_len;
2729     for (i=0; i<count; i++)
2730     {
2731         // ignore these
2732         if (data[i] == '\r')
2733             continue;
2734
2735         // join lines split by ICS?
2736         if (!appData.noJoin)
2737         {
2738             /*
2739                 Joining just consists of finding matches against the
2740                 continuation sequence, and discarding that sequence
2741                 if found instead of copying it.  So, until a match
2742                 fails, there's nothing to do since it might be the
2743                 complete sequence, and thus, something we don't want
2744                 copied.
2745             */
2746             if (data[i] == cont_seq[cmatch])
2747             {
2748                 cmatch++;
2749                 if (cmatch == strlen(cont_seq))
2750                 {
2751                     cmatch = 0; // complete match.  just reset the counter
2752
2753                     /*
2754                         it's possible for the ICS to not include the space
2755                         at the end of the last word, making our [correct]
2756                         join operation fuse two separate words.  the server
2757                         does this when the space occurs at the width setting.
2758                     */
2759                     if (!buf_len || buf[buf_len-1] != ' ')
2760                     {
2761                         *bp++ = ' ';
2762                         buf_len++;
2763                     }
2764                 }
2765                 continue;
2766             }
2767             else if (cmatch)
2768             {
2769                 /*
2770                     match failed, so we have to copy what matched before
2771                     falling through and copying this character.  In reality,
2772                     this will only ever be just the newline character, but
2773                     it doesn't hurt to be precise.
2774                 */
2775                 strncpy(bp, cont_seq, cmatch);
2776                 bp += cmatch;
2777                 buf_len += cmatch;
2778                 cmatch = 0;
2779             }
2780         }
2781
2782         // copy this char
2783         *bp++ = data[i];
2784         buf_len++;
2785     }
2786
2787         buf[buf_len] = NULLCHAR;
2788 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2789         next_out = 0;
2790         leftover_start = 0;
2791
2792         i = 0;
2793         while (i < buf_len) {
2794             /* Deal with part of the TELNET option negotiation
2795                protocol.  We refuse to do anything beyond the
2796                defaults, except that we allow the WILL ECHO option,
2797                which ICS uses to turn off password echoing when we are
2798                directly connected to it.  We reject this option
2799                if localLineEditing mode is on (always on in xboard)
2800                and we are talking to port 23, which might be a real
2801                telnet server that will try to keep WILL ECHO on permanently.
2802              */
2803             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2804                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2805                 unsigned char option;
2806                 oldi = i;
2807                 switch ((unsigned char) buf[++i]) {
2808                   case TN_WILL:
2809                     if (appData.debugMode)
2810                       fprintf(debugFP, "\n<WILL ");
2811                     switch (option = (unsigned char) buf[++i]) {
2812                       case TN_ECHO:
2813                         if (appData.debugMode)
2814                           fprintf(debugFP, "ECHO ");
2815                         /* Reply only if this is a change, according
2816                            to the protocol rules. */
2817                         if (remoteEchoOption) break;
2818                         if (appData.localLineEditing &&
2819                             atoi(appData.icsPort) == TN_PORT) {
2820                             TelnetRequest(TN_DONT, TN_ECHO);
2821                         } else {
2822                             EchoOff();
2823                             TelnetRequest(TN_DO, TN_ECHO);
2824                             remoteEchoOption = TRUE;
2825                         }
2826                         break;
2827                       default:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "%d ", option);
2830                         /* Whatever this is, we don't want it. */
2831                         TelnetRequest(TN_DONT, option);
2832                         break;
2833                     }
2834                     break;
2835                   case TN_WONT:
2836                     if (appData.debugMode)
2837                       fprintf(debugFP, "\n<WONT ");
2838                     switch (option = (unsigned char) buf[++i]) {
2839                       case TN_ECHO:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "ECHO ");
2842                         /* Reply only if this is a change, according
2843                            to the protocol rules. */
2844                         if (!remoteEchoOption) break;
2845                         EchoOn();
2846                         TelnetRequest(TN_DONT, TN_ECHO);
2847                         remoteEchoOption = FALSE;
2848                         break;
2849                       default:
2850                         if (appData.debugMode)
2851                           fprintf(debugFP, "%d ", (unsigned char) option);
2852                         /* Whatever this is, it must already be turned
2853                            off, because we never agree to turn on
2854                            anything non-default, so according to the
2855                            protocol rules, we don't reply. */
2856                         break;
2857                     }
2858                     break;
2859                   case TN_DO:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<DO ");
2862                     switch (option = (unsigned char) buf[++i]) {
2863                       default:
2864                         /* Whatever this is, we refuse to do it. */
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         TelnetRequest(TN_WONT, option);
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DONT:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DONT ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         /* Whatever this is, we are already not doing
2879                            it, because we never agree to do anything
2880                            non-default, so according to the protocol
2881                            rules, we don't reply. */
2882                         break;
2883                     }
2884                     break;
2885                   case TN_IAC:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<IAC ");
2888                     /* Doubled IAC; pass it through */
2889                     i--;
2890                     break;
2891                   default:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2894                     /* Drop all other telnet commands on the floor */
2895                     break;
2896                 }
2897                 if (oldi > next_out)
2898                   SendToPlayer(&buf[next_out], oldi - next_out);
2899                 if (++i > next_out)
2900                   next_out = i;
2901                 continue;
2902             }
2903
2904             /* OK, this at least will *usually* work */
2905             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2906                 loggedOn = TRUE;
2907             }
2908
2909             if (loggedOn && !intfSet) {
2910                 if (ics_type == ICS_ICC) {
2911                   snprintf(str, MSG_SIZ,
2912                           "/set-quietly interface %s\n/set-quietly style 12\n",
2913                           programVersion);
2914                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2916                 } else if (ics_type == ICS_CHESSNET) {
2917                   snprintf(str, MSG_SIZ, "/style 12\n");
2918                 } else {
2919                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2920                   strcat(str, programVersion);
2921                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2922                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2923                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2924 #ifdef WIN32
2925                   strcat(str, "$iset nohighlight 1\n");
2926 #endif
2927                   strcat(str, "$iset lock 1\n$style 12\n");
2928                 }
2929                 SendToICS(str);
2930                 NotifyFrontendLogin();
2931                 intfSet = TRUE;
2932             }
2933
2934             if (started == STARTED_COMMENT) {
2935                 /* Accumulate characters in comment */
2936                 parse[parse_pos++] = buf[i];
2937                 if (buf[i] == '\n') {
2938                     parse[parse_pos] = NULLCHAR;
2939                     if(chattingPartner>=0) {
2940                         char mess[MSG_SIZ];
2941                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2942                         OutputChatMessage(chattingPartner, mess);
2943                         chattingPartner = -1;
2944                         next_out = i+1; // [HGM] suppress printing in ICS window
2945                     } else
2946                     if(!suppressKibitz) // [HGM] kibitz
2947                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2948                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2949                         int nrDigit = 0, nrAlph = 0, j;
2950                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2951                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2952                         parse[parse_pos] = NULLCHAR;
2953                         // try to be smart: if it does not look like search info, it should go to
2954                         // ICS interaction window after all, not to engine-output window.
2955                         for(j=0; j<parse_pos; j++) { // count letters and digits
2956                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2957                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2958                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2959                         }
2960                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2961                             int depth=0; float score;
2962                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2963                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2964                                 pvInfoList[forwardMostMove-1].depth = depth;
2965                                 pvInfoList[forwardMostMove-1].score = 100*score;
2966                             }
2967                             OutputKibitz(suppressKibitz, parse);
2968                         } else {
2969                             char tmp[MSG_SIZ];
2970                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2971                             SendToPlayer(tmp, strlen(tmp));
2972                         }
2973                         next_out = i+1; // [HGM] suppress printing in ICS window
2974                     }
2975                     started = STARTED_NONE;
2976                 } else {
2977                     /* Don't match patterns against characters in comment */
2978                     i++;
2979                     continue;
2980                 }
2981             }
2982             if (started == STARTED_CHATTER) {
2983                 if (buf[i] != '\n') {
2984                     /* Don't match patterns against characters in chatter */
2985                     i++;
2986                     continue;
2987                 }
2988                 started = STARTED_NONE;
2989                 if(suppressKibitz) next_out = i+1;
2990             }
2991
2992             /* Kludge to deal with rcmd protocol */
2993             if (firstTime && looking_at(buf, &i, "\001*")) {
2994                 DisplayFatalError(&buf[1], 0, 1);
2995                 continue;
2996             } else {
2997                 firstTime = FALSE;
2998             }
2999
3000             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3001                 ics_type = ICS_ICC;
3002                 ics_prefix = "/";
3003                 if (appData.debugMode)
3004                   fprintf(debugFP, "ics_type %d\n", ics_type);
3005                 continue;
3006             }
3007             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3008                 ics_type = ICS_FICS;
3009                 ics_prefix = "$";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3015                 ics_type = ICS_CHESSNET;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021
3022             if (!loggedOn &&
3023                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3024                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3025                  looking_at(buf, &i, "will be \"*\""))) {
3026               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3027               continue;
3028             }
3029
3030             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3031               char buf[MSG_SIZ];
3032               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3033               DisplayIcsInteractionTitle(buf);
3034               have_set_title = TRUE;
3035             }
3036
3037             /* skip finger notes */
3038             if (started == STARTED_NONE &&
3039                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3040                  (buf[i] == '1' && buf[i+1] == '0')) &&
3041                 buf[i+2] == ':' && buf[i+3] == ' ') {
3042               started = STARTED_CHATTER;
3043               i += 3;
3044               continue;
3045             }
3046
3047             oldi = i;
3048             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3049             if(appData.seekGraph) {
3050                 if(soughtPending && MatchSoughtLine(buf+i)) {
3051                     i = strstr(buf+i, "rated") - buf;
3052                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                     next_out = leftover_start = i;
3054                     started = STARTED_CHATTER;
3055                     suppressKibitz = TRUE;
3056                     continue;
3057                 }
3058                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3059                         && looking_at(buf, &i, "* ads displayed")) {
3060                     soughtPending = FALSE;
3061                     seekGraphUp = TRUE;
3062                     DrawSeekGraph();
3063                     continue;
3064                 }
3065                 if(appData.autoRefresh) {
3066                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3067                         int s = (ics_type == ICS_ICC); // ICC format differs
3068                         if(seekGraphUp)
3069                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3070                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3071                         looking_at(buf, &i, "*% "); // eat prompt
3072                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i; // suppress
3075                         continue;
3076                     }
3077                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3078                         char *p = star_match[0];
3079                         while(*p) {
3080                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3081                             while(*p && *p++ != ' '); // next
3082                         }
3083                         looking_at(buf, &i, "*% "); // eat prompt
3084                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = i;
3086                         continue;
3087                     }
3088                 }
3089             }
3090
3091             /* skip formula vars */
3092             if (started == STARTED_NONE &&
3093                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3094               started = STARTED_CHATTER;
3095               i += 3;
3096               continue;
3097             }
3098
3099             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3100             if (appData.autoKibitz && started == STARTED_NONE &&
3101                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3102                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3103                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3104                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3105                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3106                         suppressKibitz = TRUE;
3107                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                         next_out = i;
3109                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3110                                 && (gameMode == IcsPlayingWhite)) ||
3111                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3112                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3113                             started = STARTED_CHATTER; // own kibitz we simply discard
3114                         else {
3115                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3116                             parse_pos = 0; parse[0] = NULLCHAR;
3117                             savingComment = TRUE;
3118                             suppressKibitz = gameMode != IcsObserving ? 2 :
3119                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3120                         }
3121                         continue;
3122                 } else
3123                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3124                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3125                          && atoi(star_match[0])) {
3126                     // suppress the acknowledgements of our own autoKibitz
3127                     char *p;
3128                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3129                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3130                     SendToPlayer(star_match[0], strlen(star_match[0]));
3131                     if(looking_at(buf, &i, "*% ")) // eat prompt
3132                         suppressKibitz = FALSE;
3133                     next_out = i;
3134                     continue;
3135                 }
3136             } // [HGM] kibitz: end of patch
3137
3138             // [HGM] chat: intercept tells by users for which we have an open chat window
3139             channel = -1;
3140             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141                                            looking_at(buf, &i, "* whispers:") ||
3142                                            looking_at(buf, &i, "* kibitzes:") ||
3143                                            looking_at(buf, &i, "* shouts:") ||
3144                                            looking_at(buf, &i, "* c-shouts:") ||
3145                                            looking_at(buf, &i, "--> * ") ||
3146                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3150                 int p;
3151                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152                 chattingPartner = -1;
3153
3154                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155                 for(p=0; p<MAX_CHAT; p++) {
3156                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157                     talker[0] = '['; strcat(talker, "] ");
3158                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159                     chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("kibitzes", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(!strcmp("whispers", chatPartner[p])) {
3172                         talker[0] = '['; strcat(talker, "] ");
3173                         chattingPartner = p; break;
3174                     }
3175                 } else
3176                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177                   if(buf[i-8] == '-' && buf[i-3] == 't')
3178                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179                     if(!strcmp("c-shouts", chatPartner[p])) {
3180                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                   if(chattingPartner < 0)
3185                   for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("shouts", chatPartner[p])) {
3187                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                 }
3194                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196                     talker[0] = 0; Colorize(ColorTell, FALSE);
3197                     chattingPartner = p; break;
3198                 }
3199                 if(chattingPartner<0) i = oldi; else {
3200                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203                     started = STARTED_COMMENT;
3204                     parse_pos = 0; parse[0] = NULLCHAR;
3205                     savingComment = 3 + chattingPartner; // counts as TRUE
3206                     suppressKibitz = TRUE;
3207                     continue;
3208                 }
3209             } // [HGM] chat: end of patch
3210
3211           backup = i;
3212             if (appData.zippyTalk || appData.zippyPlay) {
3213                 /* [DM] Backup address for color zippy lines */
3214 #if ZIPPY
3215                if (loggedOn == TRUE)
3216                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3218 #endif
3219             } // [DM] 'else { ' deleted
3220                 if (
3221                     /* Regular tells and says */
3222                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3224                     looking_at(buf, &i, "* says: ") ||
3225                     /* Don't color "message" or "messages" output */
3226                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227                     looking_at(buf, &i, "*. * at *:*: ") ||
3228                     looking_at(buf, &i, "--* (*:*): ") ||
3229                     /* Message notifications (same color as tells) */
3230                     looking_at(buf, &i, "* has left a message ") ||
3231                     looking_at(buf, &i, "* just sent you a message:\n") ||
3232                     /* Whispers and kibitzes */
3233                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234                     looking_at(buf, &i, "* kibitzes: ") ||
3235                     /* Channel tells */
3236                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3237
3238                   if (tkind == 1 && strchr(star_match[0], ':')) {
3239                       /* Avoid "tells you:" spoofs in channels */
3240                      tkind = 3;
3241                   }
3242                   if (star_match[0][0] == NULLCHAR ||
3243                       strchr(star_match[0], ' ') ||
3244                       (tkind == 3 && strchr(star_match[1], ' '))) {
3245                     /* Reject bogus matches */
3246                     i = oldi;
3247                   } else {
3248                     if (appData.colorize) {
3249                       if (oldi > next_out) {
3250                         SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = oldi;
3252                       }
3253                       switch (tkind) {
3254                       case 1:
3255                         Colorize(ColorTell, FALSE);
3256                         curColor = ColorTell;
3257                         break;
3258                       case 2:
3259                         Colorize(ColorKibitz, FALSE);
3260                         curColor = ColorKibitz;
3261                         break;
3262                       case 3:
3263                         p = strrchr(star_match[1], '(');
3264                         if (p == NULL) {
3265                           p = star_match[1];
3266                         } else {
3267                           p++;
3268                         }
3269                         if (atoi(p) == 1) {
3270                           Colorize(ColorChannel1, FALSE);
3271                           curColor = ColorChannel1;
3272                         } else {
3273                           Colorize(ColorChannel, FALSE);
3274                           curColor = ColorChannel;
3275                         }
3276                         break;
3277                       case 5:
3278                         curColor = ColorNormal;
3279                         break;
3280                       }
3281                     }
3282                     if (started == STARTED_NONE && appData.autoComment &&
3283                         (gameMode == IcsObserving ||
3284                          gameMode == IcsPlayingWhite ||
3285                          gameMode == IcsPlayingBlack)) {
3286                       parse_pos = i - oldi;
3287                       memcpy(parse, &buf[oldi], parse_pos);
3288                       parse[parse_pos] = NULLCHAR;
3289                       started = STARTED_COMMENT;
3290                       savingComment = TRUE;
3291                     } else {
3292                       started = STARTED_CHATTER;
3293                       savingComment = FALSE;
3294                     }
3295                     loggedOn = TRUE;
3296                     continue;
3297                   }
3298                 }
3299
3300                 if (looking_at(buf, &i, "* s-shouts: ") ||
3301                     looking_at(buf, &i, "* c-shouts: ")) {
3302                     if (appData.colorize) {
3303                         if (oldi > next_out) {
3304                             SendToPlayer(&buf[next_out], oldi - next_out);
3305                             next_out = oldi;
3306                         }
3307                         Colorize(ColorSShout, FALSE);
3308                         curColor = ColorSShout;
3309                     }
3310                     loggedOn = TRUE;
3311                     started = STARTED_CHATTER;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "--->")) {
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* shouts: ") ||
3321                     looking_at(buf, &i, "--> ")) {
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorShout, FALSE);
3328                         curColor = ColorShout;
3329                     }
3330                     loggedOn = TRUE;
3331                     started = STARTED_CHATTER;
3332                     continue;
3333                 }
3334
3335                 if (looking_at( buf, &i, "Challenge:")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorChallenge, FALSE);
3342                         curColor = ColorChallenge;
3343                     }
3344                     loggedOn = TRUE;
3345                     continue;
3346                 }
3347
3348                 if (looking_at(buf, &i, "* offers you") ||
3349                     looking_at(buf, &i, "* offers to be") ||
3350                     looking_at(buf, &i, "* would like to") ||
3351                     looking_at(buf, &i, "* requests to") ||
3352                     looking_at(buf, &i, "Your opponent offers") ||
3353                     looking_at(buf, &i, "Your opponent requests")) {
3354
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorRequest, FALSE);
3361                         curColor = ColorRequest;
3362                     }
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* (*) seeking")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSeek, FALSE);
3373                         curColor = ColorSeek;
3374                     }
3375                     continue;
3376             }
3377
3378           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3379
3380             if (looking_at(buf, &i, "\\   ")) {
3381                 if (prevColor != ColorNormal) {
3382                     if (oldi > next_out) {
3383                         SendToPlayer(&buf[next_out], oldi - next_out);
3384                         next_out = oldi;
3385                     }
3386                     Colorize(prevColor, TRUE);
3387                     curColor = prevColor;
3388                 }
3389                 if (savingComment) {
3390                     parse_pos = i - oldi;
3391                     memcpy(parse, &buf[oldi], parse_pos);
3392                     parse[parse_pos] = NULLCHAR;
3393                     started = STARTED_COMMENT;
3394                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395                         chattingPartner = savingComment - 3; // kludge to remember the box
3396                 } else {
3397                     started = STARTED_CHATTER;
3398                 }
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "Black Strength :") ||
3403                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404                 looking_at(buf, &i, "<10>") ||
3405                 looking_at(buf, &i, "#@#")) {
3406                 /* Wrong board style */
3407                 loggedOn = TRUE;
3408                 SendToICS(ics_prefix);
3409                 SendToICS("set style 12\n");
3410                 SendToICS(ics_prefix);
3411                 SendToICS("refresh\n");
3412                 continue;
3413             }
3414
3415             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3416                 ICSInitScript();
3417                 have_sent_ICS_logon = 1;
3418                 continue;
3419             }
3420
3421             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422                 (looking_at(buf, &i, "\n<12> ") ||
3423                  looking_at(buf, &i, "<12> "))) {
3424                 loggedOn = TRUE;
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_BOARD;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435                 looking_at(buf, &i, "<b1> ")) {
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_HOLDINGS;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3446                 loggedOn = TRUE;
3447                 /* Header for a move list -- first line */
3448
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     switch (gameMode) {
3452                       case IcsIdle:
3453                       case BeginningOfGame:
3454                         /* User typed "moves" or "oldmoves" while we
3455                            were idle.  Pretend we asked for these
3456                            moves and soak them up so user can step
3457                            through them and/or save them.
3458                            */
3459                         Reset(FALSE, TRUE);
3460                         gameMode = IcsObserving;
3461                         ModeHighlight();
3462                         ics_gamenum = -1;
3463                         ics_getting_history = H_GOT_UNREQ_HEADER;
3464                         break;
3465                       case EditGame: /*?*/
3466                       case EditPosition: /*?*/
3467                         /* Should above feature work in these modes too? */
3468                         /* For now it doesn't */
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                       default:
3472                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3473                         break;
3474                     }
3475                     break;
3476                   case H_REQUESTED:
3477                     /* Is this the right one? */
3478                     if (gameInfo.white && gameInfo.black &&
3479                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3480                         strcmp(gameInfo.black, star_match[2]) == 0) {
3481                         /* All is well */
3482                         ics_getting_history = H_GOT_REQ_HEADER;
3483                     }
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                   case H_GOT_UNREQ_HEADER:
3487                   case H_GOT_UNWANTED_HEADER:
3488                   case H_GETTING_MOVES:
3489                     /* Should not happen */
3490                     DisplayError(_("Error gathering move list: two headers"), 0);
3491                     ics_getting_history = H_FALSE;
3492                     break;
3493                 }
3494
3495                 /* Save player ratings into gameInfo if needed */
3496                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498                     (gameInfo.whiteRating == -1 ||
3499                      gameInfo.blackRating == -1)) {
3500
3501                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3502                     gameInfo.blackRating = string_to_rating(star_match[3]);
3503                     if (appData.debugMode)
3504                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505                               gameInfo.whiteRating, gameInfo.blackRating);
3506                 }
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i,
3511               "* * match, initial time: * minute*, increment: * second")) {
3512                 /* Header for a move list -- second line */
3513                 /* Initial board will follow if this is a wild game */
3514                 if (gameInfo.event != NULL) free(gameInfo.event);
3515                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516                 gameInfo.event = StrSave(str);
3517                 /* [HGM] we switched variant. Translate boards if needed. */
3518                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Move  ")) {
3523                 /* Beginning of a move list */
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     /* Normally should not happen */
3527                     /* Maybe user hit reset while we were parsing */
3528                     break;
3529                   case H_REQUESTED:
3530                     /* Happens if we are ignoring a move list that is not
3531                      * the one we just requested.  Common if the user
3532                      * tries to observe two games without turning off
3533                      * getMoveList */
3534                     break;
3535                   case H_GETTING_MOVES:
3536                     /* Should not happen */
3537                     DisplayError(_("Error gathering move list: nested"), 0);
3538                     ics_getting_history = H_FALSE;
3539                     break;
3540                   case H_GOT_REQ_HEADER:
3541                     ics_getting_history = H_GETTING_MOVES;
3542                     started = STARTED_MOVES;
3543                     parse_pos = 0;
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                     }
3547                     break;
3548                   case H_GOT_UNREQ_HEADER:
3549                     ics_getting_history = H_GETTING_MOVES;
3550                     started = STARTED_MOVES_NOHIDE;
3551                     parse_pos = 0;
3552                     break;
3553                   case H_GOT_UNWANTED_HEADER:
3554                     ics_getting_history = H_FALSE;
3555                     break;
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "% ") ||
3561                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564                     soughtPending = FALSE;
3565                     seekGraphUp = TRUE;
3566                     DrawSeekGraph();
3567                 }
3568                 if(suppressKibitz) next_out = i;
3569                 savingComment = FALSE;
3570                 suppressKibitz = 0;
3571                 switch (started) {
3572                   case STARTED_MOVES:
3573                   case STARTED_MOVES_NOHIDE:
3574                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575                     parse[parse_pos + i - oldi] = NULLCHAR;
3576                     ParseGameHistory(parse);
3577 #if ZIPPY
3578                     if (appData.zippyPlay && first.initDone) {
3579                         FeedMovesToProgram(&first, forwardMostMove);
3580                         if (gameMode == IcsPlayingWhite) {
3581                             if (WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("black\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, TRUE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, TRUE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         } else if (gameMode == IcsPlayingBlack) {
3605                             if (!WhiteOnMove(forwardMostMove)) {
3606                                 if (first.sendTime) {
3607                                   if (first.useColors) {
3608                                     SendToProgram("white\n", &first);
3609                                   }
3610                                   SendTimeRemaining(&first, FALSE);
3611                                 }
3612                                 if (first.useColors) {
3613                                   SendToProgram("black\n", &first);
3614                                 }
3615                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616                                 first.maybeThinking = TRUE;
3617                             } else {
3618                                 if (first.usePlayother) {
3619                                   if (first.sendTime) {
3620                                     SendTimeRemaining(&first, FALSE);
3621                                   }
3622                                   SendToProgram("playother\n", &first);
3623                                   firstMove = FALSE;
3624                                 } else {
3625                                   firstMove = TRUE;
3626                                 }
3627                             }
3628                         }
3629                     }
3630 #endif
3631                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3632                         /* Moves came from oldmoves or moves command
3633                            while we weren't doing anything else.
3634                            */
3635                         currentMove = forwardMostMove;
3636                         ClearHighlights();/*!!could figure this out*/
3637                         flipView = appData.flipView;
3638                         DrawPosition(TRUE, boards[currentMove]);
3639                         DisplayBothClocks();
3640                         snprintf(str, MSG_SIZ, "%s vs. %s",
3641                                 gameInfo.white, gameInfo.black);
3642                         DisplayTitle(str);
3643                         gameMode = IcsIdle;
3644                     } else {
3645                         /* Moves were history of an active game */
3646                         if (gameInfo.resultDetails != NULL) {
3647                             free(gameInfo.resultDetails);
3648                             gameInfo.resultDetails = NULL;
3649                         }
3650                     }
3651                     HistorySet(parseList, backwardMostMove,
3652                                forwardMostMove, currentMove-1);
3653                     DisplayMove(currentMove - 1);
3654                     if (started == STARTED_MOVES) next_out = i;
3655                     started = STARTED_NONE;
3656                     ics_getting_history = H_FALSE;
3657                     break;
3658
3659                   case STARTED_OBSERVE:
3660                     started = STARTED_NONE;
3661                     SendToICS(ics_prefix);
3662                     SendToICS("refresh\n");
3663                     break;
3664
3665                   default:
3666                     break;
3667                 }
3668                 if(bookHit) { // [HGM] book: simulate book reply
3669                     static char bookMove[MSG_SIZ]; // a bit generous?
3670
3671                     programStats.nodes = programStats.depth = programStats.time =
3672                     programStats.score = programStats.got_only_move = 0;
3673                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3674
3675                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676                     strcat(bookMove, bookHit);
3677                     HandleMachineMove(bookMove, &first);
3678                 }
3679                 continue;
3680             }
3681
3682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683                  started == STARTED_HOLDINGS ||
3684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685                 /* Accumulate characters in move list or board */
3686                 parse[parse_pos++] = buf[i];
3687             }
3688
3689             /* Start of game messages.  Mostly we detect start of game
3690                when the first board image arrives.  On some versions
3691                of the ICS, though, we need to do a "refresh" after starting
3692                to observe in order to get the current board right away. */
3693             if (looking_at(buf, &i, "Adding game * to observation list")) {
3694                 started = STARTED_OBSERVE;
3695                 continue;
3696             }
3697
3698             /* Handle auto-observe */
3699             if (appData.autoObserve &&
3700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3702                 char *player;
3703                 /* Choose the player that was highlighted, if any. */
3704                 if (star_match[0][0] == '\033' ||
3705                     star_match[1][0] != '\033') {
3706                     player = star_match[0];
3707                 } else {
3708                     player = star_match[2];
3709                 }
3710                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711                         ics_prefix, StripHighlightAndTitle(player));
3712                 SendToICS(str);
3713
3714                 /* Save ratings from notify string */
3715                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716                 player1Rating = string_to_rating(star_match[1]);
3717                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718                 player2Rating = string_to_rating(star_match[3]);
3719
3720                 if (appData.debugMode)
3721                   fprintf(debugFP,
3722                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3723                           player1Name, player1Rating,
3724                           player2Name, player2Rating);
3725
3726                 continue;
3727             }
3728
3729             /* Deal with automatic examine mode after a game,
3730                and with IcsObserving -> IcsExamining transition */
3731             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732                 looking_at(buf, &i, "has made you an examiner of game *")) {
3733
3734                 int gamenum = atoi(star_match[0]);
3735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736                     gamenum == ics_gamenum) {
3737                     /* We were already playing or observing this game;
3738                        no need to refetch history */
3739                     gameMode = IcsExamining;
3740                     if (pausing) {
3741                         pauseExamForwardMostMove = forwardMostMove;
3742                     } else if (currentMove < forwardMostMove) {
3743                         ForwardInner(forwardMostMove);
3744                     }
3745                 } else {
3746                     /* I don't think this case really can happen */
3747                     SendToICS(ics_prefix);
3748                     SendToICS("refresh\n");
3749                 }
3750                 continue;
3751             }
3752
3753             /* Error messages */
3754 //          if (ics_user_moved) {
3755             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756                 if (looking_at(buf, &i, "Illegal move") ||
3757                     looking_at(buf, &i, "Not a legal move") ||
3758                     looking_at(buf, &i, "Your king is in check") ||
3759                     looking_at(buf, &i, "It isn't your turn") ||
3760                     looking_at(buf, &i, "It is not your move")) {
3761                     /* Illegal move */
3762                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763                         currentMove = forwardMostMove-1;
3764                         DisplayMove(currentMove - 1); /* before DMError */
3765                         DrawPosition(FALSE, boards[currentMove]);
3766                         SwitchClocks(forwardMostMove-1); // [HGM] race
3767                         DisplayBothClocks();
3768                     }
3769                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3770                     ics_user_moved = 0;
3771                     continue;
3772                 }
3773             }
3774
3775             if (looking_at(buf, &i, "still have time") ||
3776                 looking_at(buf, &i, "not out of time") ||
3777                 looking_at(buf, &i, "either player is out of time") ||
3778                 looking_at(buf, &i, "has timeseal; checking")) {
3779                 /* We must have called his flag a little too soon */
3780                 whiteFlag = blackFlag = FALSE;
3781                 continue;
3782             }
3783
3784             if (looking_at(buf, &i, "added * seconds to") ||
3785                 looking_at(buf, &i, "seconds were added to")) {
3786                 /* Update the clocks */
3787                 SendToICS(ics_prefix);
3788                 SendToICS("refresh\n");
3789                 continue;
3790             }
3791
3792             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793                 ics_clock_paused = TRUE;
3794                 StopClocks();
3795                 continue;
3796             }
3797
3798             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799                 ics_clock_paused = FALSE;
3800                 StartClocks();
3801                 continue;
3802             }
3803
3804             /* Grab player ratings from the Creating: message.
3805                Note we have to check for the special case when
3806                the ICS inserts things like [white] or [black]. */
3807             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3809                 /* star_matches:
3810                    0    player 1 name (not necessarily white)
3811                    1    player 1 rating
3812                    2    empty, white, or black (IGNORED)
3813                    3    player 2 name (not necessarily black)
3814                    4    player 2 rating
3815
3816                    The names/ratings are sorted out when the game
3817                    actually starts (below).
3818                 */
3819                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820                 player1Rating = string_to_rating(star_match[1]);
3821                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822                 player2Rating = string_to_rating(star_match[4]);
3823
3824                 if (appData.debugMode)
3825                   fprintf(debugFP,
3826                           "Ratings from 'Creating:' %s %d, %s %d\n",
3827                           player1Name, player1Rating,
3828                           player2Name, player2Rating);
3829
3830                 continue;
3831             }
3832
3833             /* Improved generic start/end-of-game messages */
3834             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836                 /* If tkind == 0: */
3837                 /* star_match[0] is the game number */
3838                 /*           [1] is the white player's name */
3839                 /*           [2] is the black player's name */
3840                 /* For end-of-game: */
3841                 /*           [3] is the reason for the game end */
3842                 /*           [4] is a PGN end game-token, preceded by " " */
3843                 /* For start-of-game: */
3844                 /*           [3] begins with "Creating" or "Continuing" */
3845                 /*           [4] is " *" or empty (don't care). */
3846                 int gamenum = atoi(star_match[0]);
3847                 char *whitename, *blackname, *why, *endtoken;
3848                 ChessMove endtype = EndOfFile;
3849
3850                 if (tkind == 0) {
3851                   whitename = star_match[1];
3852                   blackname = star_match[2];
3853                   why = star_match[3];
3854                   endtoken = star_match[4];
3855                 } else {
3856                   whitename = star_match[1];
3857                   blackname = star_match[3];
3858                   why = star_match[5];
3859                   endtoken = star_match[6];
3860                 }
3861
3862                 /* Game start messages */
3863                 if (strncmp(why, "Creating ", 9) == 0 ||
3864                     strncmp(why, "Continuing ", 11) == 0) {
3865                     gs_gamenum = gamenum;
3866                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
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 == NULL) {
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
4956     switch (moveType) {
4957       default:
4958         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4959                 (int)moveType, fromX, fromY, toX, toY);
4960         DisplayError(user_move + strlen("say "), 0);
4961         break;
4962       case WhiteKingSideCastle:
4963       case BlackKingSideCastle:
4964       case WhiteQueenSideCastleWild:
4965       case BlackQueenSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteHSideCastleFR:
4968       case BlackHSideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o\n");
4971         break;
4972       case WhiteQueenSideCastle:
4973       case BlackQueenSideCastle:
4974       case WhiteKingSideCastleWild:
4975       case BlackKingSideCastleWild:
4976       /* PUSH Fabien */
4977       case WhiteASideCastleFR:
4978       case BlackASideCastleFR:
4979       /* POP Fabien */
4980         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4981         break;
4982       case WhiteNonPromotion:
4983       case BlackNonPromotion:
4984         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4985         break;
4986       case WhitePromotion:
4987       case BlackPromotion:
4988         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4989           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4990                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4991                 PieceToChar(WhiteFerz));
4992         else if(gameInfo.variant == VariantGreat)
4993           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteMan));
4996         else
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 promoChar);
5000         break;
5001       case WhiteDrop:
5002       case BlackDrop:
5003       drop:
5004         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5005                  ToUpper(PieceToChar((ChessSquare) fromX)),
5006                  AAA + toX, ONE + toY);
5007         break;
5008       case IllegalMove:  /* could be a variant we don't quite understand */
5009         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5010       case NormalMove:
5011       case WhiteCapturesEnPassant:
5012       case BlackCapturesEnPassant:
5013         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5014                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5015         break;
5016     }
5017     SendToICS(user_move);
5018     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5019         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5020 }
5021
5022 void
5023 UploadGameEvent()
5024 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5025     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5026     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5027     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5028         DisplayError("You cannot do this while you are playing or observing", 0);
5029         return;
5030     }
5031     if(gameMode != IcsExamining) { // is this ever not the case?
5032         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5033
5034         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5035           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5036         } else { // on FICS we must first go to general examine mode
5037           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5038         }
5039         if(gameInfo.variant != VariantNormal) {
5040             // try figure out wild number, as xboard names are not always valid on ICS
5041             for(i=1; i<=36; i++) {
5042               snprintf(buf, MSG_SIZ, "wild/%d", i);
5043                 if(StringToVariant(buf) == gameInfo.variant) break;
5044             }
5045             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5046             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5047             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5048         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5049         SendToICS(ics_prefix);
5050         SendToICS(buf);
5051         if(startedFromSetupPosition || backwardMostMove != 0) {
5052           fen = PositionToFEN(backwardMostMove, NULL);
5053           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5054             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5055             SendToICS(buf);
5056           } else { // FICS: everything has to set by separate bsetup commands
5057             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5058             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5059             SendToICS(buf);
5060             if(!WhiteOnMove(backwardMostMove)) {
5061                 SendToICS("bsetup tomove black\n");
5062             }
5063             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5064             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5065             SendToICS(buf);
5066             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5067             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5068             SendToICS(buf);
5069             i = boards[backwardMostMove][EP_STATUS];
5070             if(i >= 0) { // set e.p.
5071               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5072                 SendToICS(buf);
5073             }
5074             bsetup++;
5075           }
5076         }
5077       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5078             SendToICS("bsetup done\n"); // switch to normal examining.
5079     }
5080     for(i = backwardMostMove; i<last; i++) {
5081         char buf[20];
5082         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5083         SendToICS(buf);
5084     }
5085     SendToICS(ics_prefix);
5086     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5087 }
5088
5089 void
5090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5091      int rf, ff, rt, ft;
5092      char promoChar;
5093      char move[7];
5094 {
5095     if (rf == DROP_RANK) {
5096       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5097       sprintf(move, "%c@%c%c\n",
5098                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5099     } else {
5100         if (promoChar == 'x' || promoChar == NULLCHAR) {
5101           sprintf(move, "%c%c%c%c\n",
5102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5103         } else {
5104             sprintf(move, "%c%c%c%c%c\n",
5105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5106         }
5107     }
5108 }
5109
5110 void
5111 ProcessICSInitScript(f)
5112      FILE *f;
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep(int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = 1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int PromoScroll(int x, int y)
5149 {
5150   int step = 0;
5151
5152   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5153   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5154   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5155   if(!step) return FALSE;
5156   lastX = x; lastY = y;
5157   if((promoSweep < BlackPawn) == flipView) step = -step;
5158   if(step > 0) selectFlag = 1;
5159   if(!selectFlag) Sweep(step);
5160   return FALSE;
5161 }
5162
5163 void
5164 NextPiece(int step)
5165 {
5166     ChessSquare piece = boards[currentMove][toY][toX];
5167     do {
5168         pieceSweep -= step;
5169         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5170         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5171         if(!step) step = -1;
5172     } while(PieceToChar(pieceSweep) == '.');
5173     boards[currentMove][toY][toX] = pieceSweep;
5174     DrawPosition(FALSE, boards[currentMove]);
5175     boards[currentMove][toY][toX] = piece;
5176 }
5177 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5178 void
5179 AlphaRank(char *move, int n)
5180 {
5181 //    char *p = move, c; int x, y;
5182
5183     if (appData.debugMode) {
5184         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5185     }
5186
5187     if(move[1]=='*' &&
5188        move[2]>='0' && move[2]<='9' &&
5189        move[3]>='a' && move[3]<='x'    ) {
5190         move[1] = '@';
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[0]>='0' && move[0]<='9' &&
5195        move[1]>='a' && move[1]<='x' &&
5196        move[2]>='0' && move[2]<='9' &&
5197        move[3]>='a' && move[3]<='x'    ) {
5198         /* input move, Shogi -> normal */
5199         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5200         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5203     } else
5204     if(move[1]=='@' &&
5205        move[3]>='0' && move[3]<='9' &&
5206        move[2]>='a' && move[2]<='x'    ) {
5207         move[1] = '*';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210     } else
5211     if(
5212        move[0]>='a' && move[0]<='x' &&
5213        move[3]>='0' && move[3]<='9' &&
5214        move[2]>='a' && move[2]<='x'    ) {
5215          /* output move, normal -> Shogi */
5216         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5217         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5220         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5221     }
5222     if (appData.debugMode) {
5223         fprintf(debugFP, "   out = '%s'\n", move);
5224     }
5225 }
5226
5227 char yy_textstr[8000];
5228
5229 /* Parser for moves from gnuchess, ICS, or user typein box */
5230 Boolean
5231 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5232      char *move;
5233      int moveNum;
5234      ChessMove *moveType;
5235      int *fromX, *fromY, *toX, *toY;
5236      char *promoChar;
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336 if(appData.debugMode){
5337 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);
5338 }
5339     if(!valid && nr == 0 &&
5340        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5341         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5342         // Hande case where played move is different from leading PV move
5343         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5344         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5345         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5346         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5347           endPV += 2; // if position different, keep this
5348           moveList[endPV-1][0] = fromX + AAA;
5349           moveList[endPV-1][1] = fromY + ONE;
5350           moveList[endPV-1][2] = toX + AAA;
5351           moveList[endPV-1][3] = toY + ONE;
5352           parseList[endPV-1][0] = NULLCHAR;
5353           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5354         }
5355       }
5356     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5357     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5358     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5359     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5360         valid++; // allow comments in PV
5361         continue;
5362     }
5363     nr++;
5364     if(endPV+1 > framePtr) break; // no space, truncate
5365     if(!valid) break;
5366     endPV++;
5367     CopyBoard(boards[endPV], boards[endPV-1]);
5368     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5369     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5370     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5371     CoordsToAlgebraic(boards[endPV - 1],
5372                              PosFlags(endPV - 1),
5373                              fromY, fromX, toY, toX, promoChar,
5374                              parseList[endPV - 1]);
5375   } while(valid);
5376   if(atEnd == 2) return; // used hidden, for PV conversion
5377   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5378   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5379   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5380                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5381   DrawPosition(TRUE, boards[currentMove]);
5382 }
5383
5384 int
5385 MultiPV(ChessProgramState *cps)
5386 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5387         int i;
5388         for(i=0; i<cps->nrOptions; i++)
5389             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5390                 return i;
5391         return -1;
5392 }
5393
5394 Boolean
5395 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5396 {
5397         int startPV, multi, lineStart, origIndex = index;
5398         char *p, buf2[MSG_SIZ];
5399
5400         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5401         lastX = x; lastY = y;
5402         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5403         lineStart = startPV = index;
5404         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5405         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5406         index = startPV;
5407         do{ while(buf[index] && buf[index] != '\n') index++;
5408         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5409         buf[index] = 0;
5410         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5411                 int n = first.option[multi].value;
5412                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5413                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5414                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5415                 first.option[multi].value = n;
5416                 *start = *end = 0;
5417                 return FALSE;
5418         }
5419         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5420         *start = startPV; *end = index-1;
5421         return TRUE;
5422 }
5423
5424 char *
5425 PvToSAN(char *pv)
5426 {
5427         static char buf[10*MSG_SIZ];
5428         int i, k=0, savedEnd=endPV;
5429         *buf = NULLCHAR;
5430         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5431         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5432         for(i = forwardMostMove; i<endPV; i++){
5433             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5434             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5435             k += strlen(buf+k);
5436         }
5437         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5438         if(forwardMostMove < savedEnd) PopInner(0);
5439         endPV = savedEnd;
5440         return buf;
5441 }
5442
5443 Boolean
5444 LoadPV(int x, int y)
5445 { // called on right mouse click to load PV
5446   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5447   lastX = x; lastY = y;
5448   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5449   return TRUE;
5450 }
5451
5452 void
5453 UnLoadPV()
5454 {
5455   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5456   if(endPV < 0) return;
5457   endPV = -1;
5458   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5459         Boolean saveAnimate = appData.animate;
5460         if(pushed) {
5461             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5462                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5463             } else storedGames--; // abandon shelved tail of original game
5464         }
5465         pushed = FALSE;
5466         forwardMostMove = currentMove;
5467         currentMove = oldFMM;
5468         appData.animate = FALSE;
5469         ToNrEvent(forwardMostMove);
5470         appData.animate = saveAnimate;
5471   }
5472   currentMove = forwardMostMove;
5473   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5474   ClearPremoveHighlights();
5475   DrawPosition(TRUE, boards[currentMove]);
5476 }
5477
5478 void
5479 MovePV(int x, int y, int h)
5480 { // step through PV based on mouse coordinates (called on mouse move)
5481   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5482
5483   // we must somehow check if right button is still down (might be released off board!)
5484   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5485   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5486   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5487   if(!step) return;
5488   lastX = x; lastY = y;
5489
5490   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5491   if(endPV < 0) return;
5492   if(y < margin) step = 1; else
5493   if(y > h - margin) step = -1;
5494   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5495   currentMove += step;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(FALSE, boards[currentMove]);
5500 }
5501
5502
5503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5504 // All positions will have equal probability, but the current method will not provide a unique
5505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5506 #define DARK 1
5507 #define LITE 2
5508 #define ANY 3
5509
5510 int squaresLeft[4];
5511 int piecesLeft[(int)BlackPawn];
5512 int seed, nrOfShuffles;
5513
5514 void GetPositionNumber()
5515 {       // sets global variable seed
5516         int i;
5517
5518         seed = appData.defaultFrcPosition;
5519         if(seed < 0) { // randomize based on time for negative FRC position numbers
5520                 for(i=0; i<50; i++) seed += random();
5521                 seed = random() ^ random() >> 8 ^ random() << 8;
5522                 if(seed<0) seed = -seed;
5523         }
5524 }
5525
5526 int put(Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5545 // calculate where the next piece goes, (any empty square), and put it there
5546 {
5547         int i;
5548
5549         i = seed % squaresLeft[shade];
5550         nrOfShuffles *= squaresLeft[shade];
5551         seed /= squaresLeft[shade];
5552         put(board, pieceType, rank, i, shade);
5553 }
5554
5555 void AddTwoPieces(Board board, int pieceType, int rank)
5556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5557 {
5558         int i, n=squaresLeft[ANY], j=n-1, k;
5559
5560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5561         i = seed % k;  // pick one
5562         nrOfShuffles *= k;
5563         seed /= k;
5564         while(i >= j) i -= j--;
5565         j = n - 1 - j; i += j;
5566         put(board, pieceType, rank, j, ANY);
5567         put(board, pieceType, rank, i, ANY);
5568 }
5569
5570 void SetUpShuffle(Board board, int number)
5571 {
5572         int i, p, first=1;
5573
5574         GetPositionNumber(); nrOfShuffles = 1;
5575
5576         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5577         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5578         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5579
5580         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5581
5582         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5583             p = (int) board[0][i];
5584             if(p < (int) BlackPawn) piecesLeft[p] ++;
5585             board[0][i] = EmptySquare;
5586         }
5587
5588         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5589             // shuffles restricted to allow normal castling put KRR first
5590             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5591                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5593                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5595                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5596             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5597                 put(board, WhiteRook, 0, 0, ANY);
5598             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5599         }
5600
5601         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5602             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5603             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5604                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5605                 while(piecesLeft[p] >= 2) {
5606                     AddOnePiece(board, p, 0, LITE);
5607                     AddOnePiece(board, p, 0, DARK);
5608                 }
5609                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5610             }
5611
5612         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5613             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5614             // but we leave King and Rooks for last, to possibly obey FRC restriction
5615             if(p == (int)WhiteRook) continue;
5616             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5617             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5618         }
5619
5620         // now everything is placed, except perhaps King (Unicorn) and Rooks
5621
5622         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5623             // Last King gets castling rights
5624             while(piecesLeft[(int)WhiteUnicorn]) {
5625                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5626                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5627             }
5628
5629             while(piecesLeft[(int)WhiteKing]) {
5630                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634
5635         } else {
5636             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5637             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5638         }
5639
5640         // Only Rooks can be left; simply place them all
5641         while(piecesLeft[(int)WhiteRook]) {
5642                 i = put(board, WhiteRook, 0, 0, ANY);
5643                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5644                         if(first) {
5645                                 first=0;
5646                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5647                         }
5648                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5649                 }
5650         }
5651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5652             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5653         }
5654
5655         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5656 }
5657
5658 int SetCharTable( char *table, const char * map )
5659 /* [HGM] moved here from winboard.c because of its general usefulness */
5660 /*       Basically a safe strcpy that uses the last character as King */
5661 {
5662     int result = FALSE; int NrPieces;
5663
5664     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5665                     && NrPieces >= 12 && !(NrPieces&1)) {
5666         int i; /* [HGM] Accept even length from 12 to 34 */
5667
5668         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5669         for( i=0; i<NrPieces/2-1; i++ ) {
5670             table[i] = map[i];
5671             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5672         }
5673         table[(int) WhiteKing]  = map[NrPieces/2-1];
5674         table[(int) BlackKing]  = map[NrPieces-1];
5675
5676         result = TRUE;
5677     }
5678
5679     return result;
5680 }
5681
5682 void Prelude(Board board)
5683 {       // [HGM] superchess: random selection of exo-pieces
5684         int i, j, k; ChessSquare p;
5685         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5686
5687         GetPositionNumber(); // use FRC position number
5688
5689         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5690             SetCharTable(pieceToChar, appData.pieceToCharTable);
5691             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5692                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5693         }
5694
5695         j = seed%4;                 seed /= 4;
5696         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5697         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5698         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5699         j = seed%3 + (seed%3 >= j); seed /= 3;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3;                 seed /= 3;
5704         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%2 + (seed%2 >= j); seed /= 2;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5712         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5713         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5714         put(board, exoPieces[0],    0, 0, ANY);
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5716 }
5717
5718 void
5719 InitPosition(redraw)
5720      int redraw;
5721 {
5722     ChessSquare (* pieces)[BOARD_FILES];
5723     int i, j, pawnRow, overrule,
5724     oldx = gameInfo.boardWidth,
5725     oldy = gameInfo.boardHeight,
5726     oldh = gameInfo.holdingsWidth;
5727     static int oldv;
5728
5729     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5730
5731     /* [AS] Initialize pv info list [HGM] and game status */
5732     {
5733         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5734             pvInfoList[i].depth = 0;
5735             boards[i][EP_STATUS] = EP_NONE;
5736             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5737         }
5738
5739         initialRulePlies = 0; /* 50-move counter start */
5740
5741         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5742         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5743     }
5744
5745
5746     /* [HGM] logic here is completely changed. In stead of full positions */
5747     /* the initialized data only consist of the two backranks. The switch */
5748     /* selects which one we will use, which is than copied to the Board   */
5749     /* initialPosition, which for the rest is initialized by Pawns and    */
5750     /* empty squares. This initial position is then copied to boards[0],  */
5751     /* possibly after shuffling, so that it remains available.            */
5752
5753     gameInfo.holdingsWidth = 0; /* default board sizes */
5754     gameInfo.boardWidth    = 8;
5755     gameInfo.boardHeight   = 8;
5756     gameInfo.holdingsSize  = 0;
5757     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5758     for(i=0; i<BOARD_FILES-2; i++)
5759       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5760     initialPosition[EP_STATUS] = EP_NONE;
5761     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5762     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5763          SetCharTable(pieceNickName, appData.pieceNickNames);
5764     else SetCharTable(pieceNickName, "............");
5765     pieces = FIDEArray;
5766
5767     switch (gameInfo.variant) {
5768     case VariantFischeRandom:
5769       shuffleOpenings = TRUE;
5770     default:
5771       break;
5772     case VariantShatranj:
5773       pieces = ShatranjArray;
5774       nrCastlingRights = 0;
5775       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5776       break;
5777     case VariantMakruk:
5778       pieces = makrukArray;
5779       nrCastlingRights = 0;
5780       startedFromSetupPosition = TRUE;
5781       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5782       break;
5783     case VariantTwoKings:
5784       pieces = twoKingsArray;
5785       break;
5786     case VariantGrand:
5787       pieces = GrandArray;
5788       nrCastlingRights = 0;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       gameInfo.boardWidth = 10;
5791       gameInfo.boardHeight = 10;
5792       gameInfo.holdingsSize = 7;
5793       break;
5794     case VariantCapaRandom:
5795       shuffleOpenings = TRUE;
5796     case VariantCapablanca:
5797       pieces = CapablancaArray;
5798       gameInfo.boardWidth = 10;
5799       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5800       break;
5801     case VariantGothic:
5802       pieces = GothicArray;
5803       gameInfo.boardWidth = 10;
5804       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5805       break;
5806     case VariantSChess:
5807       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5808       gameInfo.holdingsSize = 7;
5809       break;
5810     case VariantJanus:
5811       pieces = JanusArray;
5812       gameInfo.boardWidth = 10;
5813       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5814       nrCastlingRights = 6;
5815         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5816         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5817         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5818         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5819         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5820         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5821       break;
5822     case VariantFalcon:
5823       pieces = FalconArray;
5824       gameInfo.boardWidth = 10;
5825       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5826       break;
5827     case VariantXiangqi:
5828       pieces = XiangqiArray;
5829       gameInfo.boardWidth  = 9;
5830       gameInfo.boardHeight = 10;
5831       nrCastlingRights = 0;
5832       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5833       break;
5834     case VariantShogi:
5835       pieces = ShogiArray;
5836       gameInfo.boardWidth  = 9;
5837       gameInfo.boardHeight = 9;
5838       gameInfo.holdingsSize = 7;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5841       break;
5842     case VariantCourier:
5843       pieces = CourierArray;
5844       gameInfo.boardWidth  = 12;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5847       break;
5848     case VariantKnightmate:
5849       pieces = KnightmateArray;
5850       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5851       break;
5852     case VariantSpartan:
5853       pieces = SpartanArray;
5854       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5855       break;
5856     case VariantFairy:
5857       pieces = fairyArray;
5858       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5859       break;
5860     case VariantGreat:
5861       pieces = GreatArray;
5862       gameInfo.boardWidth = 10;
5863       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5864       gameInfo.holdingsSize = 8;
5865       break;
5866     case VariantSuper:
5867       pieces = FIDEArray;
5868       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5869       gameInfo.holdingsSize = 8;
5870       startedFromSetupPosition = TRUE;
5871       break;
5872     case VariantCrazyhouse:
5873     case VariantBughouse:
5874       pieces = FIDEArray;
5875       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5876       gameInfo.holdingsSize = 5;
5877       break;
5878     case VariantWildCastle:
5879       pieces = FIDEArray;
5880       /* !!?shuffle with kings guaranteed to be on d or e file */
5881       shuffleOpenings = 1;
5882       break;
5883     case VariantNoCastle:
5884       pieces = FIDEArray;
5885       nrCastlingRights = 0;
5886       /* !!?unconstrained back-rank shuffle */
5887       shuffleOpenings = 1;
5888       break;
5889     }
5890
5891     overrule = 0;
5892     if(appData.NrFiles >= 0) {
5893         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5894         gameInfo.boardWidth = appData.NrFiles;
5895     }
5896     if(appData.NrRanks >= 0) {
5897         gameInfo.boardHeight = appData.NrRanks;
5898     }
5899     if(appData.holdingsSize >= 0) {
5900         i = appData.holdingsSize;
5901         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5902         gameInfo.holdingsSize = i;
5903     }
5904     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5905     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5906         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5907
5908     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5909     if(pawnRow < 1) pawnRow = 1;
5910     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5911
5912     /* User pieceToChar list overrules defaults */
5913     if(appData.pieceToCharTable != NULL)
5914         SetCharTable(pieceToChar, appData.pieceToCharTable);
5915
5916     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5917
5918         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5919             s = (ChessSquare) 0; /* account holding counts in guard band */
5920         for( i=0; i<BOARD_HEIGHT; i++ )
5921             initialPosition[i][j] = s;
5922
5923         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5924         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5925         initialPosition[pawnRow][j] = WhitePawn;
5926         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5927         if(gameInfo.variant == VariantXiangqi) {
5928             if(j&1) {
5929                 initialPosition[pawnRow][j] =
5930                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5931                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5932                    initialPosition[2][j] = WhiteCannon;
5933                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5934                 }
5935             }
5936         }
5937         if(gameInfo.variant == VariantGrand) {
5938             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5939                initialPosition[0][j] = WhiteRook;
5940                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5941             }
5942         }
5943         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5944     }
5945     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5946
5947             j=BOARD_LEFT+1;
5948             initialPosition[1][j] = WhiteBishop;
5949             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5950             j=BOARD_RGHT-2;
5951             initialPosition[1][j] = WhiteRook;
5952             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5953     }
5954
5955     if( nrCastlingRights == -1) {
5956         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5957         /*       This sets default castling rights from none to normal corners   */
5958         /* Variants with other castling rights must set them themselves above    */
5959         nrCastlingRights = 6;
5960
5961         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5964         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5967      }
5968
5969      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5970      if(gameInfo.variant == VariantGreat) { // promotion commoners
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5972         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5974         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5975      }
5976      if( gameInfo.variant == VariantSChess ) {
5977       initialPosition[1][0] = BlackMarshall;
5978       initialPosition[2][0] = BlackAngel;
5979       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5980       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5981       initialPosition[1][1] = initialPosition[2][1] = 
5982       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5983      }
5984   if (appData.debugMode) {
5985     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5986   }
5987     if(shuffleOpenings) {
5988         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5989         startedFromSetupPosition = TRUE;
5990     }
5991     if(startedFromPositionFile) {
5992       /* [HGM] loadPos: use PositionFile for every new game */
5993       CopyBoard(initialPosition, filePosition);
5994       for(i=0; i<nrCastlingRights; i++)
5995           initialRights[i] = filePosition[CASTLING][i];
5996       startedFromSetupPosition = TRUE;
5997     }
5998
5999     CopyBoard(boards[0], initialPosition);
6000
6001     if(oldx != gameInfo.boardWidth ||
6002        oldy != gameInfo.boardHeight ||
6003        oldv != gameInfo.variant ||
6004        oldh != gameInfo.holdingsWidth
6005                                          )
6006             InitDrawingSizes(-2 ,0);
6007
6008     oldv = gameInfo.variant;
6009     if (redraw)
6010       DrawPosition(TRUE, boards[currentMove]);
6011 }
6012
6013 void
6014 SendBoard(cps, moveNum)
6015      ChessProgramState *cps;
6016      int moveNum;
6017 {
6018     char message[MSG_SIZ];
6019
6020     if (cps->useSetboard) {
6021       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6022       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6023       SendToProgram(message, cps);
6024       free(fen);
6025
6026     } else {
6027       ChessSquare *bp;
6028       int i, j;
6029       /* Kludge to set black to move, avoiding the troublesome and now
6030        * deprecated "black" command.
6031        */
6032       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6033         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6034
6035       SendToProgram("edit\n", cps);
6036       SendToProgram("#\n", cps);
6037       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6038         bp = &boards[moveNum][i][BOARD_LEFT];
6039         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6040           if ((int) *bp < (int) BlackPawn) {
6041             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6042                     AAA + j, ONE + i);
6043             if(message[0] == '+' || message[0] == '~') {
6044               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6045                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6046                         AAA + j, ONE + i);
6047             }
6048             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6049                 message[1] = BOARD_RGHT   - 1 - j + '1';
6050                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6051             }
6052             SendToProgram(message, cps);
6053           }
6054         }
6055       }
6056
6057       SendToProgram("c\n", cps);
6058       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6059         bp = &boards[moveNum][i][BOARD_LEFT];
6060         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6061           if (((int) *bp != (int) EmptySquare)
6062               && ((int) *bp >= (int) BlackPawn)) {
6063             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6064                     AAA + j, ONE + i);
6065             if(message[0] == '+' || message[0] == '~') {
6066               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6067                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6068                         AAA + j, ONE + i);
6069             }
6070             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6071                 message[1] = BOARD_RGHT   - 1 - j + '1';
6072                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6073             }
6074             SendToProgram(message, cps);
6075           }
6076         }
6077       }
6078
6079       SendToProgram(".\n", cps);
6080     }
6081     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6082 }
6083
6084 ChessSquare
6085 DefaultPromoChoice(int white)
6086 {
6087     ChessSquare result;
6088     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6089         result = WhiteFerz; // no choice
6090     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6091         result= WhiteKing; // in Suicide Q is the last thing we want
6092     else if(gameInfo.variant == VariantSpartan)
6093         result = white ? WhiteQueen : WhiteAngel;
6094     else result = WhiteQueen;
6095     if(!white) result = WHITE_TO_BLACK result;
6096     return result;
6097 }
6098
6099 static int autoQueen; // [HGM] oneclick
6100
6101 int
6102 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6103 {
6104     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6105     /* [HGM] add Shogi promotions */
6106     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6107     ChessSquare piece;
6108     ChessMove moveType;
6109     Boolean premove;
6110
6111     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6112     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6113
6114     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6115       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6116         return FALSE;
6117
6118     piece = boards[currentMove][fromY][fromX];
6119     if(gameInfo.variant == VariantShogi) {
6120         promotionZoneSize = BOARD_HEIGHT/3;
6121         highestPromotingPiece = (int)WhiteFerz;
6122     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6123         promotionZoneSize = 3;
6124     }
6125
6126     // Treat Lance as Pawn when it is not representing Amazon
6127     if(gameInfo.variant != VariantSuper) {
6128         if(piece == WhiteLance) piece = WhitePawn; else
6129         if(piece == BlackLance) piece = BlackPawn;
6130     }
6131
6132     // next weed out all moves that do not touch the promotion zone at all
6133     if((int)piece >= BlackPawn) {
6134         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6135              return FALSE;
6136         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6137     } else {
6138         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6139            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6140     }
6141
6142     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6143
6144     // weed out mandatory Shogi promotions
6145     if(gameInfo.variant == VariantShogi) {
6146         if(piece >= BlackPawn) {
6147             if(toY == 0 && piece == BlackPawn ||
6148                toY == 0 && piece == BlackQueen ||
6149                toY <= 1 && piece == BlackKnight) {
6150                 *promoChoice = '+';
6151                 return FALSE;
6152             }
6153         } else {
6154             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6155                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6156                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6157                 *promoChoice = '+';
6158                 return FALSE;
6159             }
6160         }
6161     }
6162
6163     // weed out obviously illegal Pawn moves
6164     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6165         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6166         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6167         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6168         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6169         // note we are not allowed to test for valid (non-)capture, due to premove
6170     }
6171
6172     // we either have a choice what to promote to, or (in Shogi) whether to promote
6173     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6174         *promoChoice = PieceToChar(BlackFerz);  // no choice
6175         return FALSE;
6176     }
6177     // no sense asking what we must promote to if it is going to explode...
6178     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6179         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6180         return FALSE;
6181     }
6182     // give caller the default choice even if we will not make it
6183     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6184     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6185     if(        sweepSelect && gameInfo.variant != VariantGreat
6186                            && gameInfo.variant != VariantGrand
6187                            && gameInfo.variant != VariantSuper) return FALSE;
6188     if(autoQueen) return FALSE; // predetermined
6189
6190     // suppress promotion popup on illegal moves that are not premoves
6191     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6192               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6193     if(appData.testLegality && !premove) {
6194         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6195                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6196         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6197             return FALSE;
6198     }
6199
6200     return TRUE;
6201 }
6202
6203 int
6204 InPalace(row, column)
6205      int row, column;
6206 {   /* [HGM] for Xiangqi */
6207     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6208          column < (BOARD_WIDTH + 4)/2 &&
6209          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6210     return FALSE;
6211 }
6212
6213 int
6214 PieceForSquare (x, y)
6215      int x;
6216      int y;
6217 {
6218   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6219      return -1;
6220   else
6221      return boards[currentMove][y][x];
6222 }
6223
6224 int
6225 OKToStartUserMove(x, y)
6226      int x, y;
6227 {
6228     ChessSquare from_piece;
6229     int white_piece;
6230
6231     if (matchMode) return FALSE;
6232     if (gameMode == EditPosition) return TRUE;
6233
6234     if (x >= 0 && y >= 0)
6235       from_piece = boards[currentMove][y][x];
6236     else
6237       from_piece = EmptySquare;
6238
6239     if (from_piece == EmptySquare) return FALSE;
6240
6241     white_piece = (int)from_piece >= (int)WhitePawn &&
6242       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6243
6244     switch (gameMode) {
6245       case AnalyzeFile:
6246       case TwoMachinesPlay:
6247       case EndOfGame:
6248         return FALSE;
6249
6250       case IcsObserving:
6251       case IcsIdle:
6252         return FALSE;
6253
6254       case MachinePlaysWhite:
6255       case IcsPlayingBlack:
6256         if (appData.zippyPlay) return FALSE;
6257         if (white_piece) {
6258             DisplayMoveError(_("You are playing Black"));
6259             return FALSE;
6260         }
6261         break;
6262
6263       case MachinePlaysBlack:
6264       case IcsPlayingWhite:
6265         if (appData.zippyPlay) return FALSE;
6266         if (!white_piece) {
6267             DisplayMoveError(_("You are playing White"));
6268             return FALSE;
6269         }
6270         break;
6271
6272       case PlayFromGameFile:
6273             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6274       case EditGame:
6275         if (!white_piece && WhiteOnMove(currentMove)) {
6276             DisplayMoveError(_("It is White's turn"));
6277             return FALSE;
6278         }
6279         if (white_piece && !WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return FALSE;
6282         }
6283         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6284             /* Editing correspondence game history */
6285             /* Could disallow this or prompt for confirmation */
6286             cmailOldMove = -1;
6287         }
6288         break;
6289
6290       case BeginningOfGame:
6291         if (appData.icsActive) return FALSE;
6292         if (!appData.noChessProgram) {
6293             if (!white_piece) {
6294                 DisplayMoveError(_("You are playing White"));
6295                 return FALSE;
6296             }
6297         }
6298         break;
6299
6300       case Training:
6301         if (!white_piece && WhiteOnMove(currentMove)) {
6302             DisplayMoveError(_("It is White's turn"));
6303             return FALSE;
6304         }
6305         if (white_piece && !WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is Black's turn"));
6307             return FALSE;
6308         }
6309         break;
6310
6311       default:
6312       case IcsExamining:
6313         break;
6314     }
6315     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6316         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6317         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6318         && gameMode != AnalyzeFile && gameMode != Training) {
6319         DisplayMoveError(_("Displayed position is not current"));
6320         return FALSE;
6321     }
6322     return TRUE;
6323 }
6324
6325 Boolean
6326 OnlyMove(int *x, int *y, Boolean captures) {
6327     DisambiguateClosure cl;
6328     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6329     switch(gameMode) {
6330       case MachinePlaysBlack:
6331       case IcsPlayingWhite:
6332       case BeginningOfGame:
6333         if(!WhiteOnMove(currentMove)) return FALSE;
6334         break;
6335       case MachinePlaysWhite:
6336       case IcsPlayingBlack:
6337         if(WhiteOnMove(currentMove)) return FALSE;
6338         break;
6339       case EditGame:
6340         break;
6341       default:
6342         return FALSE;
6343     }
6344     cl.pieceIn = EmptySquare;
6345     cl.rfIn = *y;
6346     cl.ffIn = *x;
6347     cl.rtIn = -1;
6348     cl.ftIn = -1;
6349     cl.promoCharIn = NULLCHAR;
6350     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6351     if( cl.kind == NormalMove ||
6352         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6353         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6354         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355       fromX = cl.ff;
6356       fromY = cl.rf;
6357       *x = cl.ft;
6358       *y = cl.rt;
6359       return TRUE;
6360     }
6361     if(cl.kind != ImpossibleMove) return FALSE;
6362     cl.pieceIn = EmptySquare;
6363     cl.rfIn = -1;
6364     cl.ffIn = -1;
6365     cl.rtIn = *y;
6366     cl.ftIn = *x;
6367     cl.promoCharIn = NULLCHAR;
6368     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6369     if( cl.kind == NormalMove ||
6370         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6371         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6372         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6373       fromX = cl.ff;
6374       fromY = cl.rf;
6375       *x = cl.ft;
6376       *y = cl.rt;
6377       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6378       return TRUE;
6379     }
6380     return FALSE;
6381 }
6382
6383 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6384 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6385 int lastLoadGameUseList = FALSE;
6386 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6387 ChessMove lastLoadGameStart = EndOfFile;
6388
6389 void
6390 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6391      int fromX, fromY, toX, toY;
6392      int promoChar;
6393 {
6394     ChessMove moveType;
6395     ChessSquare pdown, pup;
6396
6397     /* Check if the user is playing in turn.  This is complicated because we
6398        let the user "pick up" a piece before it is his turn.  So the piece he
6399        tried to pick up may have been captured by the time he puts it down!
6400        Therefore we use the color the user is supposed to be playing in this
6401        test, not the color of the piece that is currently on the starting
6402        square---except in EditGame mode, where the user is playing both
6403        sides; fortunately there the capture race can't happen.  (It can
6404        now happen in IcsExamining mode, but that's just too bad.  The user
6405        will get a somewhat confusing message in that case.)
6406        */
6407
6408     switch (gameMode) {
6409       case AnalyzeFile:
6410       case TwoMachinesPlay:
6411       case EndOfGame:
6412       case IcsObserving:
6413       case IcsIdle:
6414         /* We switched into a game mode where moves are not accepted,
6415            perhaps while the mouse button was down. */
6416         return;
6417
6418       case MachinePlaysWhite:
6419         /* User is moving for Black */
6420         if (WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is White's turn"));
6422             return;
6423         }
6424         break;
6425
6426       case MachinePlaysBlack:
6427         /* User is moving for White */
6428         if (!WhiteOnMove(currentMove)) {
6429             DisplayMoveError(_("It is Black's turn"));
6430             return;
6431         }
6432         break;
6433
6434       case PlayFromGameFile:
6435             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6436       case EditGame:
6437       case IcsExamining:
6438       case BeginningOfGame:
6439       case AnalyzeMode:
6440       case Training:
6441         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6442         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6443             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6444             /* User is moving for Black */
6445             if (WhiteOnMove(currentMove)) {
6446                 DisplayMoveError(_("It is White's turn"));
6447                 return;
6448             }
6449         } else {
6450             /* User is moving for White */
6451             if (!WhiteOnMove(currentMove)) {
6452                 DisplayMoveError(_("It is Black's turn"));
6453                 return;
6454             }
6455         }
6456         break;
6457
6458       case IcsPlayingBlack:
6459         /* User is moving for Black */
6460         if (WhiteOnMove(currentMove)) {
6461             if (!appData.premove) {
6462                 DisplayMoveError(_("It is White's turn"));
6463             } else if (toX >= 0 && toY >= 0) {
6464                 premoveToX = toX;
6465                 premoveToY = toY;
6466                 premoveFromX = fromX;
6467                 premoveFromY = fromY;
6468                 premovePromoChar = promoChar;
6469                 gotPremove = 1;
6470                 if (appData.debugMode)
6471                     fprintf(debugFP, "Got premove: fromX %d,"
6472                             "fromY %d, toX %d, toY %d\n",
6473                             fromX, fromY, toX, toY);
6474             }
6475             return;
6476         }
6477         break;
6478
6479       case IcsPlayingWhite:
6480         /* User is moving for White */
6481         if (!WhiteOnMove(currentMove)) {
6482             if (!appData.premove) {
6483                 DisplayMoveError(_("It is Black's turn"));
6484             } else if (toX >= 0 && toY >= 0) {
6485                 premoveToX = toX;
6486                 premoveToY = toY;
6487                 premoveFromX = fromX;
6488                 premoveFromY = fromY;
6489                 premovePromoChar = promoChar;
6490                 gotPremove = 1;
6491                 if (appData.debugMode)
6492                     fprintf(debugFP, "Got premove: fromX %d,"
6493                             "fromY %d, toX %d, toY %d\n",
6494                             fromX, fromY, toX, toY);
6495             }
6496             return;
6497         }
6498         break;
6499
6500       default:
6501         break;
6502
6503       case EditPosition:
6504         /* EditPosition, empty square, or different color piece;
6505            click-click move is possible */
6506         if (toX == -2 || toY == -2) {
6507             boards[0][fromY][fromX] = EmptySquare;
6508             DrawPosition(FALSE, boards[currentMove]);
6509             return;
6510         } else if (toX >= 0 && toY >= 0) {
6511             boards[0][toY][toX] = boards[0][fromY][fromX];
6512             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6513                 if(boards[0][fromY][0] != EmptySquare) {
6514                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6515                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6516                 }
6517             } else
6518             if(fromX == BOARD_RGHT+1) {
6519                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6520                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6521                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6522                 }
6523             } else
6524             boards[0][fromY][fromX] = EmptySquare;
6525             DrawPosition(FALSE, boards[currentMove]);
6526             return;
6527         }
6528         return;
6529     }
6530
6531     if(toX < 0 || toY < 0) return;
6532     pdown = boards[currentMove][fromY][fromX];
6533     pup = boards[currentMove][toY][toX];
6534
6535     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6536     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6537          if( pup != EmptySquare ) return;
6538          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6539            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6540                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6541            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6542            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6543            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6544            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6545          fromY = DROP_RANK;
6546     }
6547
6548     /* [HGM] always test for legality, to get promotion info */
6549     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6550                                          fromY, fromX, toY, toX, promoChar);
6551
6552     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6553
6554     /* [HGM] but possibly ignore an IllegalMove result */
6555     if (appData.testLegality) {
6556         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6557             DisplayMoveError(_("Illegal move"));
6558             return;
6559         }
6560     }
6561
6562     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6563 }
6564
6565 /* Common tail of UserMoveEvent and DropMenuEvent */
6566 int
6567 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6568      ChessMove moveType;
6569      int fromX, fromY, toX, toY;
6570      /*char*/int promoChar;
6571 {
6572     char *bookHit = 0;
6573
6574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6575         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6576         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6577         if(WhiteOnMove(currentMove)) {
6578             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6579         } else {
6580             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6581         }
6582     }
6583
6584     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6585        move type in caller when we know the move is a legal promotion */
6586     if(moveType == NormalMove && promoChar)
6587         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6588
6589     /* [HGM] <popupFix> The following if has been moved here from
6590        UserMoveEvent(). Because it seemed to belong here (why not allow
6591        piece drops in training games?), and because it can only be
6592        performed after it is known to what we promote. */
6593     if (gameMode == Training) {
6594       /* compare the move played on the board to the next move in the
6595        * game. If they match, display the move and the opponent's response.
6596        * If they don't match, display an error message.
6597        */
6598       int saveAnimate;
6599       Board testBoard;
6600       CopyBoard(testBoard, boards[currentMove]);
6601       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6602
6603       if (CompareBoards(testBoard, boards[currentMove+1])) {
6604         ForwardInner(currentMove+1);
6605
6606         /* Autoplay the opponent's response.
6607          * if appData.animate was TRUE when Training mode was entered,
6608          * the response will be animated.
6609          */
6610         saveAnimate = appData.animate;
6611         appData.animate = animateTraining;
6612         ForwardInner(currentMove+1);
6613         appData.animate = saveAnimate;
6614
6615         /* check for the end of the game */
6616         if (currentMove >= forwardMostMove) {
6617           gameMode = PlayFromGameFile;
6618           ModeHighlight();
6619           SetTrainingModeOff();
6620           DisplayInformation(_("End of game"));
6621         }
6622       } else {
6623         DisplayError(_("Incorrect move"), 0);
6624       }
6625       return 1;
6626     }
6627
6628   /* Ok, now we know that the move is good, so we can kill
6629      the previous line in Analysis Mode */
6630   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6631                                 && currentMove < forwardMostMove) {
6632     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6633     else forwardMostMove = currentMove;
6634   }
6635
6636   /* If we need the chess program but it's dead, restart it */
6637   ResurrectChessProgram();
6638
6639   /* A user move restarts a paused game*/
6640   if (pausing)
6641     PauseEvent();
6642
6643   thinkOutput[0] = NULLCHAR;
6644
6645   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6646
6647   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6648     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649     return 1;
6650   }
6651
6652   if (gameMode == BeginningOfGame) {
6653     if (appData.noChessProgram) {
6654       gameMode = EditGame;
6655       SetGameInfo();
6656     } else {
6657       char buf[MSG_SIZ];
6658       gameMode = MachinePlaysBlack;
6659       StartClocks();
6660       SetGameInfo();
6661       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6662       DisplayTitle(buf);
6663       if (first.sendName) {
6664         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6665         SendToProgram(buf, &first);
6666       }
6667       StartClocks();
6668     }
6669     ModeHighlight();
6670   }
6671
6672   /* Relay move to ICS or chess engine */
6673   if (appData.icsActive) {
6674     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6675         gameMode == IcsExamining) {
6676       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6677         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6678         SendToICS("draw ");
6679         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       }
6681       // also send plain move, in case ICS does not understand atomic claims
6682       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       ics_user_moved = 1;
6684     }
6685   } else {
6686     if (first.sendTime && (gameMode == BeginningOfGame ||
6687                            gameMode == MachinePlaysWhite ||
6688                            gameMode == MachinePlaysBlack)) {
6689       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6690     }
6691     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6692          // [HGM] book: if program might be playing, let it use book
6693         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6694         first.maybeThinking = TRUE;
6695     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6696         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6697         SendBoard(&first, currentMove+1);
6698     } else SendMoveToProgram(forwardMostMove-1, &first);
6699     if (currentMove == cmailOldMove + 1) {
6700       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6701     }
6702   }
6703
6704   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705
6706   switch (gameMode) {
6707   case EditGame:
6708     if(appData.testLegality)
6709     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6710     case MT_NONE:
6711     case MT_CHECK:
6712       break;
6713     case MT_CHECKMATE:
6714     case MT_STAINMATE:
6715       if (WhiteOnMove(currentMove)) {
6716         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6717       } else {
6718         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6719       }
6720       break;
6721     case MT_STALEMATE:
6722       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6723       break;
6724     }
6725     break;
6726
6727   case MachinePlaysBlack:
6728   case MachinePlaysWhite:
6729     /* disable certain menu options while machine is thinking */
6730     SetMachineThinkingEnables();
6731     break;
6732
6733   default:
6734     break;
6735   }
6736
6737   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6738   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6739
6740   if(bookHit) { // [HGM] book: simulate book reply
6741         static char bookMove[MSG_SIZ]; // a bit generous?
6742
6743         programStats.nodes = programStats.depth = programStats.time =
6744         programStats.score = programStats.got_only_move = 0;
6745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6746
6747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6748         strcat(bookMove, bookHit);
6749         HandleMachineMove(bookMove, &first);
6750   }
6751   return 1;
6752 }
6753
6754 void
6755 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6756      Board board;
6757      int flags;
6758      ChessMove kind;
6759      int rf, ff, rt, ft;
6760      VOIDSTAR closure;
6761 {
6762     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6763     Markers *m = (Markers *) closure;
6764     if(rf == fromY && ff == fromX)
6765         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6766                          || kind == WhiteCapturesEnPassant
6767                          || kind == BlackCapturesEnPassant);
6768     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6769 }
6770
6771 void
6772 MarkTargetSquares(int clear)
6773 {
6774   int x, y;
6775   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6776      !appData.testLegality || gameMode == EditPosition) return;
6777   if(clear) {
6778     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6779   } else {
6780     int capt = 0;
6781     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6782     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6784       if(capt)
6785       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6786     }
6787   }
6788   DrawPosition(TRUE, NULL);
6789 }
6790
6791 int
6792 Explode(Board board, int fromX, int fromY, int toX, int toY)
6793 {
6794     if(gameInfo.variant == VariantAtomic &&
6795        (board[toY][toX] != EmptySquare ||                     // capture?
6796         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6797                          board[fromY][fromX] == BlackPawn   )
6798       )) {
6799         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6800         return TRUE;
6801     }
6802     return FALSE;
6803 }
6804
6805 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6806
6807 int CanPromote(ChessSquare piece, int y)
6808 {
6809         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6810         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6811         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6812            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6813            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6814                                                   gameInfo.variant == VariantMakruk) return FALSE;
6815         return (piece == BlackPawn && y == 1 ||
6816                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6817                 piece == BlackLance && y == 1 ||
6818                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6819 }
6820
6821 void LeftClick(ClickType clickType, int xPix, int yPix)
6822 {
6823     int x, y;
6824     Boolean saveAnimate;
6825     static int second = 0, promotionChoice = 0, clearFlag = 0;
6826     char promoChoice = NULLCHAR;
6827     ChessSquare piece;
6828
6829     if(appData.seekGraph && appData.icsActive && loggedOn &&
6830         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6831         SeekGraphClick(clickType, xPix, yPix, 0);
6832         return;
6833     }
6834
6835     if (clickType == Press) ErrorPopDown();
6836
6837     x = EventToSquare(xPix, BOARD_WIDTH);
6838     y = EventToSquare(yPix, BOARD_HEIGHT);
6839     if (!flipView && y >= 0) {
6840         y = BOARD_HEIGHT - 1 - y;
6841     }
6842     if (flipView && x >= 0) {
6843         x = BOARD_WIDTH - 1 - x;
6844     }
6845
6846     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6847         defaultPromoChoice = promoSweep;
6848         promoSweep = EmptySquare;   // terminate sweep
6849         promoDefaultAltered = TRUE;
6850         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6851     }
6852
6853     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6854         if(clickType == Release) return; // ignore upclick of click-click destination
6855         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6856         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6857         if(gameInfo.holdingsWidth &&
6858                 (WhiteOnMove(currentMove)
6859                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6860                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6861             // click in right holdings, for determining promotion piece
6862             ChessSquare p = boards[currentMove][y][x];
6863             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6864             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6865             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6866                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6867                 fromX = fromY = -1;
6868                 return;
6869             }
6870         }
6871         DrawPosition(FALSE, boards[currentMove]);
6872         return;
6873     }
6874
6875     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6876     if(clickType == Press
6877             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6878               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6879               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6880         return;
6881
6882     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6883         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6884
6885     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6886         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6887                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6888         defaultPromoChoice = DefaultPromoChoice(side);
6889     }
6890
6891     autoQueen = appData.alwaysPromoteToQueen;
6892
6893     if (fromX == -1) {
6894       int originalY = y;
6895       gatingPiece = EmptySquare;
6896       if (clickType != Press) {
6897         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6898             DragPieceEnd(xPix, yPix); dragging = 0;
6899             DrawPosition(FALSE, NULL);
6900         }
6901         return;
6902       }
6903       fromX = x; fromY = y; toX = toY = -1;
6904       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6905          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6906          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6907             /* First square */
6908             if (OKToStartUserMove(fromX, fromY)) {
6909                 second = 0;
6910                 MarkTargetSquares(0);
6911                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6912                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6913                     promoSweep = defaultPromoChoice;
6914                     selectFlag = 0; lastX = xPix; lastY = yPix;
6915                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6916                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6917                 }
6918                 if (appData.highlightDragging) {
6919                     SetHighlights(fromX, fromY, -1, -1);
6920                 }
6921             } else fromX = fromY = -1;
6922             return;
6923         }
6924     }
6925
6926     /* fromX != -1 */
6927     if (clickType == Press && gameMode != EditPosition) {
6928         ChessSquare fromP;
6929         ChessSquare toP;
6930         int frc;
6931
6932         // ignore off-board to clicks
6933         if(y < 0 || x < 0) return;
6934
6935         /* Check if clicking again on the same color piece */
6936         fromP = boards[currentMove][fromY][fromX];
6937         toP = boards[currentMove][y][x];
6938         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6939         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6940              WhitePawn <= toP && toP <= WhiteKing &&
6941              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6942              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6943             (BlackPawn <= fromP && fromP <= BlackKing &&
6944              BlackPawn <= toP && toP <= BlackKing &&
6945              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6946              !(fromP == BlackKing && toP == BlackRook && frc))) {
6947             /* Clicked again on same color piece -- changed his mind */
6948             second = (x == fromX && y == fromY);
6949             promoDefaultAltered = FALSE;
6950             MarkTargetSquares(1);
6951            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6952             if (appData.highlightDragging) {
6953                 SetHighlights(x, y, -1, -1);
6954             } else {
6955                 ClearHighlights();
6956             }
6957             if (OKToStartUserMove(x, y)) {
6958                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6959                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6960                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6961                  gatingPiece = boards[currentMove][fromY][fromX];
6962                 else gatingPiece = EmptySquare;
6963                 fromX = x;
6964                 fromY = y; dragging = 1;
6965                 MarkTargetSquares(0);
6966                 DragPieceBegin(xPix, yPix, FALSE);
6967                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6968                     promoSweep = defaultPromoChoice;
6969                     selectFlag = 0; lastX = xPix; lastY = yPix;
6970                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6971                 }
6972             }
6973            }
6974            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6975            second = FALSE; 
6976         }
6977         // ignore clicks on holdings
6978         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6979     }
6980
6981     if (clickType == Release && x == fromX && y == fromY) {
6982         DragPieceEnd(xPix, yPix); dragging = 0;
6983         if(clearFlag) {
6984             // a deferred attempt to click-click move an empty square on top of a piece
6985             boards[currentMove][y][x] = EmptySquare;
6986             ClearHighlights();
6987             DrawPosition(FALSE, boards[currentMove]);
6988             fromX = fromY = -1; clearFlag = 0;
6989             return;
6990         }
6991         if (appData.animateDragging) {
6992             /* Undo animation damage if any */
6993             DrawPosition(FALSE, NULL);
6994         }
6995         if (second) {
6996             /* Second up/down in same square; just abort move */
6997             second = 0;
6998             fromX = fromY = -1;
6999             gatingPiece = EmptySquare;
7000             ClearHighlights();
7001             gotPremove = 0;
7002             ClearPremoveHighlights();
7003         } else {
7004             /* First upclick in same square; start click-click mode */
7005             SetHighlights(x, y, -1, -1);
7006         }
7007         return;
7008     }
7009
7010     clearFlag = 0;
7011
7012     /* we now have a different from- and (possibly off-board) to-square */
7013     /* Completed move */
7014     toX = x;
7015     toY = y;
7016     saveAnimate = appData.animate;
7017     MarkTargetSquares(1);
7018     if (clickType == Press) {
7019         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7020             // must be Edit Position mode with empty-square selected
7021             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7022             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7023             return;
7024         }
7025         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7026             ChessSquare piece = boards[currentMove][fromY][fromX];
7027             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7028             promoSweep = defaultPromoChoice;
7029             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7030             selectFlag = 0; lastX = xPix; lastY = yPix;
7031             Sweep(0); // Pawn that is going to promote: preview promotion piece
7032             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7033             DrawPosition(FALSE, boards[currentMove]);
7034             return;
7035         }
7036         /* Finish clickclick move */
7037         if (appData.animate || appData.highlightLastMove) {
7038             SetHighlights(fromX, fromY, toX, toY);
7039         } else {
7040             ClearHighlights();
7041         }
7042     } else {
7043         /* Finish drag move */
7044         if (appData.highlightLastMove) {
7045             SetHighlights(fromX, fromY, toX, toY);
7046         } else {
7047             ClearHighlights();
7048         }
7049         DragPieceEnd(xPix, yPix); dragging = 0;
7050         /* Don't animate move and drag both */
7051         appData.animate = FALSE;
7052     }
7053
7054     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7055     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7056         ChessSquare piece = boards[currentMove][fromY][fromX];
7057         if(gameMode == EditPosition && piece != EmptySquare &&
7058            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7059             int n;
7060
7061             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7062                 n = PieceToNumber(piece - (int)BlackPawn);
7063                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7065                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7066             } else
7067             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7068                 n = PieceToNumber(piece);
7069                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7070                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7071                 boards[currentMove][n][BOARD_WIDTH-2]++;
7072             }
7073             boards[currentMove][fromY][fromX] = EmptySquare;
7074         }
7075         ClearHighlights();
7076         fromX = fromY = -1;
7077         DrawPosition(TRUE, boards[currentMove]);
7078         return;
7079     }
7080
7081     // off-board moves should not be highlighted
7082     if(x < 0 || y < 0) ClearHighlights();
7083
7084     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7085
7086     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7087         SetHighlights(fromX, fromY, toX, toY);
7088         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7089             // [HGM] super: promotion to captured piece selected from holdings
7090             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7091             promotionChoice = TRUE;
7092             // kludge follows to temporarily execute move on display, without promoting yet
7093             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7094             boards[currentMove][toY][toX] = p;
7095             DrawPosition(FALSE, boards[currentMove]);
7096             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7097             boards[currentMove][toY][toX] = q;
7098             DisplayMessage("Click in holdings to choose piece", "");
7099             return;
7100         }
7101         PromotionPopUp();
7102     } else {
7103         int oldMove = currentMove;
7104         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7105         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7106         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7107         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7108            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7109             DrawPosition(TRUE, boards[currentMove]);
7110         fromX = fromY = -1;
7111     }
7112     appData.animate = saveAnimate;
7113     if (appData.animate || appData.animateDragging) {
7114         /* Undo animation damage if needed */
7115         DrawPosition(FALSE, NULL);
7116     }
7117 }
7118
7119 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7120 {   // front-end-free part taken out of PieceMenuPopup
7121     int whichMenu; int xSqr, ySqr;
7122
7123     if(seekGraphUp) { // [HGM] seekgraph
7124         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7125         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7126         return -2;
7127     }
7128
7129     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7130          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7131         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7132         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7133         if(action == Press)   {
7134             originalFlip = flipView;
7135             flipView = !flipView; // temporarily flip board to see game from partners perspective
7136             DrawPosition(TRUE, partnerBoard);
7137             DisplayMessage(partnerStatus, "");
7138             partnerUp = TRUE;
7139         } else if(action == Release) {
7140             flipView = originalFlip;
7141             DrawPosition(TRUE, boards[currentMove]);
7142             partnerUp = FALSE;
7143         }
7144         return -2;
7145     }
7146
7147     xSqr = EventToSquare(x, BOARD_WIDTH);
7148     ySqr = EventToSquare(y, BOARD_HEIGHT);
7149     if (action == Release) {
7150         if(pieceSweep != EmptySquare) {
7151             EditPositionMenuEvent(pieceSweep, toX, toY);
7152             pieceSweep = EmptySquare;
7153         } else UnLoadPV(); // [HGM] pv
7154     }
7155     if (action != Press) return -2; // return code to be ignored
7156     switch (gameMode) {
7157       case IcsExamining:
7158         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7159       case EditPosition:
7160         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7161         if (xSqr < 0 || ySqr < 0) return -1;
7162         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7163         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7164         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7165         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7166         NextPiece(0);
7167         return 2; // grab
7168       case IcsObserving:
7169         if(!appData.icsEngineAnalyze) return -1;
7170       case IcsPlayingWhite:
7171       case IcsPlayingBlack:
7172         if(!appData.zippyPlay) goto noZip;
7173       case AnalyzeMode:
7174       case AnalyzeFile:
7175       case MachinePlaysWhite:
7176       case MachinePlaysBlack:
7177       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7178         if (!appData.dropMenu) {
7179           LoadPV(x, y);
7180           return 2; // flag front-end to grab mouse events
7181         }
7182         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7183            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7184       case EditGame:
7185       noZip:
7186         if (xSqr < 0 || ySqr < 0) return -1;
7187         if (!appData.dropMenu || appData.testLegality &&
7188             gameInfo.variant != VariantBughouse &&
7189             gameInfo.variant != VariantCrazyhouse) return -1;
7190         whichMenu = 1; // drop menu
7191         break;
7192       default:
7193         return -1;
7194     }
7195
7196     if (((*fromX = xSqr) < 0) ||
7197         ((*fromY = ySqr) < 0)) {
7198         *fromX = *fromY = -1;
7199         return -1;
7200     }
7201     if (flipView)
7202       *fromX = BOARD_WIDTH - 1 - *fromX;
7203     else
7204       *fromY = BOARD_HEIGHT - 1 - *fromY;
7205
7206     return whichMenu;
7207 }
7208
7209 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     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
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane(int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings(int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 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
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights(Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate(ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566         if (appData.debugMode) { int i;
7567             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7568                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7569                     appData.drawRepeats);
7570             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7571               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7572
7573         }
7574
7575         // Repetition draws and 50-move rule can be applied independently of legality testing
7576
7577                 /* Check for rep-draws */
7578                 count = 0;
7579                 for(k = forwardMostMove-2;
7580                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7581                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7582                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7583                     k-=2)
7584                 {   int rights=0;
7585                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7586                         /* compare castling rights */
7587                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7588                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7589                                 rights++; /* King lost rights, while rook still had them */
7590                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7591                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7592                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7593                                    rights++; /* but at least one rook lost them */
7594                         }
7595                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7596                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7597                                 rights++;
7598                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7599                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7600                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7601                                    rights++;
7602                         }
7603                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7604                             && appData.drawRepeats > 1) {
7605                              /* adjudicate after user-specified nr of repeats */
7606                              int result = GameIsDrawn;
7607                              char *details = "XBoard adjudication: repetition draw";
7608                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7609                                 // [HGM] xiangqi: check for forbidden perpetuals
7610                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7611                                 for(m=forwardMostMove; m>k; m-=2) {
7612                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7613                                         ourPerpetual = 0; // the current mover did not always check
7614                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7615                                         hisPerpetual = 0; // the opponent did not always check
7616                                 }
7617                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7618                                                                         ourPerpetual, hisPerpetual);
7619                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7620                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                     details = "Xboard adjudication: perpetual checking";
7622                                 } else
7623                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7624                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7625                                 } else
7626                                 // Now check for perpetual chases
7627                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7628                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7629                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7630                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7631                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7632                                         details = "Xboard adjudication: perpetual chasing";
7633                                     } else
7634                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7635                                         break; // Abort repetition-checking loop.
7636                                 }
7637                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7638                              }
7639                              if(engineOpponent) {
7640                                SendToProgram("force\n", engineOpponent); // suppress reply
7641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                              }
7643                              GameEnds( result, details, GE_XBOARD );
7644                              return 1;
7645                         }
7646                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7647                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7648                     }
7649                 }
7650
7651                 /* Now we test for 50-move draws. Determine ply count */
7652                 count = forwardMostMove;
7653                 /* look for last irreversble move */
7654                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7655                     count--;
7656                 /* if we hit starting position, add initial plies */
7657                 if( count == backwardMostMove )
7658                     count -= initialRulePlies;
7659                 count = forwardMostMove - count;
7660                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7661                         // adjust reversible move counter for checks in Xiangqi
7662                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7663                         if(i < backwardMostMove) i = backwardMostMove;
7664                         while(i <= forwardMostMove) {
7665                                 lastCheck = inCheck; // check evasion does not count
7666                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7667                                 if(inCheck || lastCheck) count--; // check does not count
7668                                 i++;
7669                         }
7670                 }
7671                 if( count >= 100)
7672                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7673                          /* this is used to judge if draw claims are legal */
7674                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7675                          if(engineOpponent) {
7676                            SendToProgram("force\n", engineOpponent); // suppress reply
7677                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                          }
7679                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7680                          return 1;
7681                 }
7682
7683                 /* if draw offer is pending, treat it as a draw claim
7684                  * when draw condition present, to allow engines a way to
7685                  * claim draws before making their move to avoid a race
7686                  * condition occurring after their move
7687                  */
7688                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7689                          char *p = NULL;
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7691                              p = "Draw claim: 50-move rule";
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7693                              p = "Draw claim: 3-fold repetition";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7695                              p = "Draw claim: insufficient mating material";
7696                          if( p != NULL && canAdjudicate) {
7697                              if(engineOpponent) {
7698                                SendToProgram("force\n", engineOpponent); // suppress reply
7699                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                              }
7701                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7702                              return 1;
7703                          }
7704                 }
7705
7706                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7707                     if(engineOpponent) {
7708                       SendToProgram("force\n", engineOpponent); // suppress reply
7709                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                     }
7711                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7712                     return 1;
7713                 }
7714         return 0;
7715 }
7716
7717 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7718 {   // [HGM] book: this routine intercepts moves to simulate book replies
7719     char *bookHit = NULL;
7720
7721     //first determine if the incoming move brings opponent into his book
7722     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7723         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7724     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7725     if(bookHit != NULL && !cps->bookSuspend) {
7726         // make sure opponent is not going to reply after receiving move to book position
7727         SendToProgram("force\n", cps);
7728         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7729     }
7730     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7731     // now arrange restart after book miss
7732     if(bookHit) {
7733         // after a book hit we never send 'go', and the code after the call to this routine
7734         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7735         char buf[MSG_SIZ], *move = bookHit;
7736         if(cps->useSAN) {
7737             int fromX, fromY, toX, toY;
7738             char promoChar;
7739             ChessMove moveType;
7740             move = buf + 30;
7741             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7744                                     PosFlags(forwardMostMove),
7745                                     fromY, fromX, toY, toX, promoChar, move);
7746             } else {
7747                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748                 bookHit = NULL;
7749             }
7750         }
7751         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7752         SendToProgram(buf, cps);
7753         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7754     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7755         SendToProgram("go\n", cps);
7756         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7757     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7758         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7759             SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7761     }
7762     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 }
7764
7765 char *savedMessage;
7766 ChessProgramState *savedState;
7767 void DeferredBookMove(void)
7768 {
7769         if(savedState->lastPing != savedState->lastPong)
7770                     ScheduleDelayedEvent(DeferredBookMove, 10);
7771         else
7772         HandleMachineMove(savedMessage, savedState);
7773 }
7774
7775 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7776
7777 void
7778 HandleMachineMove(message, cps)
7779      char *message;
7780      ChessProgramState *cps;
7781 {
7782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7783     char realname[MSG_SIZ];
7784     int fromX, fromY, toX, toY;
7785     ChessMove moveType;
7786     char promoChar;
7787     char *p, *pv=buf1;
7788     int machineWhite;
7789     char *bookHit;
7790
7791     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7792         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7793         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7794             DisplayError(_("Invalid pairing from pairing engine"), 0);
7795             return;
7796         }
7797         pairingReceived = 1;
7798         NextMatchGame();
7799         return; // Skim the pairing messages here.
7800     }
7801
7802     cps->userError = 0;
7803
7804 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7805     /*
7806      * Kludge to ignore BEL characters
7807      */
7808     while (*message == '\007') message++;
7809
7810     /*
7811      * [HGM] engine debug message: ignore lines starting with '#' character
7812      */
7813     if(cps->debug && *message == '#') return;
7814
7815     /*
7816      * Look for book output
7817      */
7818     if (cps == &first && bookRequested) {
7819         if (message[0] == '\t' || message[0] == ' ') {
7820             /* Part of the book output is here; append it */
7821             strcat(bookOutput, message);
7822             strcat(bookOutput, "  \n");
7823             return;
7824         } else if (bookOutput[0] != NULLCHAR) {
7825             /* All of book output has arrived; display it */
7826             char *p = bookOutput;
7827             while (*p != NULLCHAR) {
7828                 if (*p == '\t') *p = ' ';
7829                 p++;
7830             }
7831             DisplayInformation(bookOutput);
7832             bookRequested = FALSE;
7833             /* Fall through to parse the current output */
7834         }
7835     }
7836
7837     /*
7838      * Look for machine move.
7839      */
7840     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7841         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7842     {
7843         /* This method is only useful on engines that support ping */
7844         if (cps->lastPing != cps->lastPong) {
7845           if (gameMode == BeginningOfGame) {
7846             /* Extra move from before last new; ignore */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7849             }
7850           } else {
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7853                         cps->which, gameMode);
7854             }
7855
7856             SendToProgram("undo\n", cps);
7857           }
7858           return;
7859         }
7860
7861         switch (gameMode) {
7862           case BeginningOfGame:
7863             /* Extra move from before last reset; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867             return;
7868
7869           case EndOfGame:
7870           case IcsIdle:
7871           default:
7872             /* Extra move after we tried to stop.  The mode test is
7873                not a reliable way of detecting this problem, but it's
7874                the best we can do on engines that don't support ping.
7875             */
7876             if (appData.debugMode) {
7877                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7878                         cps->which, gameMode);
7879             }
7880             SendToProgram("undo\n", cps);
7881             return;
7882
7883           case MachinePlaysWhite:
7884           case IcsPlayingWhite:
7885             machineWhite = TRUE;
7886             break;
7887
7888           case MachinePlaysBlack:
7889           case IcsPlayingBlack:
7890             machineWhite = FALSE;
7891             break;
7892
7893           case TwoMachinesPlay:
7894             machineWhite = (cps->twoMachinesColor[0] == 'w');
7895             break;
7896         }
7897         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7898             if (appData.debugMode) {
7899                 fprintf(debugFP,
7900                         "Ignoring move out of turn by %s, gameMode %d"
7901                         ", forwardMost %d\n",
7902                         cps->which, gameMode, forwardMostMove);
7903             }
7904             return;
7905         }
7906
7907     if (appData.debugMode) { int f = forwardMostMove;
7908         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7909                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7910                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7911     }
7912         if(cps->alphaRank) AlphaRank(machineMove, 4);
7913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7915             /* Machine move could not be parsed; ignore it. */
7916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7917                     machineMove, _(cps->which));
7918             DisplayError(buf1, 0);
7919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7921             if (gameMode == TwoMachinesPlay) {
7922               GameEnds(machineWhite ? BlackWins : WhiteWins,
7923                        buf1, GE_XBOARD);
7924             }
7925             return;
7926         }
7927
7928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7929         /* So we have to redo legality test with true e.p. status here,  */
7930         /* to make sure an illegal e.p. capture does not slip through,   */
7931         /* to cause a forfeit on a justified illegal-move complaint      */
7932         /* of the opponent.                                              */
7933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7934            ChessMove moveType;
7935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7936                              fromY, fromX, toY, toX, promoChar);
7937             if (appData.debugMode) {
7938                 int i;
7939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7941                 fprintf(debugFP, "castling rights\n");
7942             }
7943             if(moveType == IllegalMove) {
7944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7947                            buf1, GE_XBOARD);
7948                 return;
7949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7950            /* [HGM] Kludge to handle engines that send FRC-style castling
7951               when they shouldn't (like TSCP-Gothic) */
7952            switch(moveType) {
7953              case WhiteASideCastleFR:
7954              case BlackASideCastleFR:
7955                toX+=2;
7956                currentMoveString[2]++;
7957                break;
7958              case WhiteHSideCastleFR:
7959              case BlackHSideCastleFR:
7960                toX--;
7961                currentMoveString[2]--;
7962                break;
7963              default: ; // nothing to do, but suppresses warning of pedantic compilers
7964            }
7965         }
7966         hintRequested = FALSE;
7967         lastHint[0] = NULLCHAR;
7968         bookRequested = FALSE;
7969         /* Program may be pondering now */
7970         cps->maybeThinking = TRUE;
7971         if (cps->sendTime == 2) cps->sendTime = 1;
7972         if (cps->offeredDraw) cps->offeredDraw--;
7973
7974         /* [AS] Save move info*/
7975         pvInfoList[ forwardMostMove ].score = programStats.score;
7976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7978
7979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7980
7981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7983             int count = 0;
7984
7985             while( count < adjudicateLossPlies ) {
7986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7987
7988                 if( count & 1 ) {
7989                     score = -score; /* Flip score for winning side */
7990                 }
7991
7992                 if( score > adjudicateLossThreshold ) {
7993                     break;
7994                 }
7995
7996                 count++;
7997             }
7998
7999             if( count >= adjudicateLossPlies ) {
8000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001
8002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8003                     "Xboard adjudication",
8004                     GE_XBOARD );
8005
8006                 return;
8007             }
8008         }
8009
8010         if(Adjudicate(cps)) {
8011             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8012             return; // [HGM] adjudicate: for all automatic game ends
8013         }
8014
8015 #if ZIPPY
8016         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8017             first.initDone) {
8018           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8019                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8020                 SendToICS("draw ");
8021                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           }
8023           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           ics_user_moved = 1;
8025           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8026                 char buf[3*MSG_SIZ];
8027
8028                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8029                         programStats.score / 100.,
8030                         programStats.depth,
8031                         programStats.time / 100.,
8032                         (unsigned int)programStats.nodes,
8033                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8034                         programStats.movelist);
8035                 SendToICS(buf);
8036 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8037           }
8038         }
8039 #endif
8040
8041         /* [AS] Clear stats for next move */
8042         ClearProgramStats();
8043         thinkOutput[0] = NULLCHAR;
8044         hiddenThinkOutputState = 0;
8045
8046         bookHit = NULL;
8047         if (gameMode == TwoMachinesPlay) {
8048             /* [HGM] relaying draw offers moved to after reception of move */
8049             /* and interpreting offer as claim if it brings draw condition */
8050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8051                 SendToProgram("draw\n", cps->other);
8052             }
8053             if (cps->other->sendTime) {
8054                 SendTimeRemaining(cps->other,
8055                                   cps->other->twoMachinesColor[0] == 'w');
8056             }
8057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8058             if (firstMove && !bookHit) {
8059                 firstMove = FALSE;
8060                 if (cps->other->useColors) {
8061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8062                 }
8063                 SendToProgram("go\n", cps->other);
8064             }
8065             cps->other->maybeThinking = TRUE;
8066         }
8067
8068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8069
8070         if (!pausing && appData.ringBellAfterMoves) {
8071             RingBell();
8072         }
8073
8074         /*
8075          * Reenable menu items that were disabled while
8076          * machine was thinking
8077          */
8078         if (gameMode != TwoMachinesPlay)
8079             SetUserThinkingEnables();
8080
8081         // [HGM] book: after book hit opponent has received move and is now in force mode
8082         // force the book reply into it, and then fake that it outputted this move by jumping
8083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8084         if(bookHit) {
8085                 static char bookMove[MSG_SIZ]; // a bit generous?
8086
8087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8088                 strcat(bookMove, bookHit);
8089                 message = bookMove;
8090                 cps = cps->other;
8091                 programStats.nodes = programStats.depth = programStats.time =
8092                 programStats.score = programStats.got_only_move = 0;
8093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8094
8095                 if(cps->lastPing != cps->lastPong) {
8096                     savedMessage = message; // args for deferred call
8097                     savedState = cps;
8098                     ScheduleDelayedEvent(DeferredBookMove, 10);
8099                     return;
8100                 }
8101                 goto FakeBookMove;
8102         }
8103
8104         return;
8105     }
8106
8107     /* Set special modes for chess engines.  Later something general
8108      *  could be added here; for now there is just one kludge feature,
8109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8110      *  when "xboard" is given as an interactive command.
8111      */
8112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8113         cps->useSigint = FALSE;
8114         cps->useSigterm = FALSE;
8115     }
8116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8117       ParseFeatures(message+8, cps);
8118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8119     }
8120
8121     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8122       int dummy, s=6; char buf[MSG_SIZ];
8123       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8124       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8125       ParseFEN(boards[0], &dummy, message+s);
8126       DrawPosition(TRUE, boards[0]);
8127       startedFromSetupPosition = TRUE;
8128       return;
8129     }
8130     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8131      * want this, I was asked to put it in, and obliged.
8132      */
8133     if (!strncmp(message, "setboard ", 9)) {
8134         Board initial_position;
8135
8136         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8137
8138         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8139             DisplayError(_("Bad FEN received from engine"), 0);
8140             return ;
8141         } else {
8142            Reset(TRUE, FALSE);
8143            CopyBoard(boards[0], initial_position);
8144            initialRulePlies = FENrulePlies;
8145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8146            else gameMode = MachinePlaysBlack;
8147            DrawPosition(FALSE, boards[currentMove]);
8148         }
8149         return;
8150     }
8151
8152     /*
8153      * Look for communication commands
8154      */
8155     if (!strncmp(message, "telluser ", 9)) {
8156         if(message[9] == '\\' && message[10] == '\\')
8157             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8158         PlayTellSound();
8159         DisplayNote(message + 9);
8160         return;
8161     }
8162     if (!strncmp(message, "tellusererror ", 14)) {
8163         cps->userError = 1;
8164         if(message[14] == '\\' && message[15] == '\\')
8165             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8166         PlayTellSound();
8167         DisplayError(message + 14, 0);
8168         return;
8169     }
8170     if (!strncmp(message, "tellopponent ", 13)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 13);
8178       }
8179       return;
8180     }
8181     if (!strncmp(message, "tellothers ", 11)) {
8182       if (appData.icsActive) {
8183         if (loggedOn) {
8184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8185           SendToICS(buf1);
8186         }
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellall ", 8)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8194           SendToICS(buf1);
8195         }
8196       } else {
8197         DisplayNote(message + 8);
8198       }
8199       return;
8200     }
8201     if (strncmp(message, "warning", 7) == 0) {
8202         /* Undocumented feature, use tellusererror in new code */
8203         DisplayError(message, 0);
8204         return;
8205     }
8206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8207         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8208         strcat(realname, " query");
8209         AskQuestion(realname, buf2, buf1, cps->pr);
8210         return;
8211     }
8212     /* Commands from the engine directly to ICS.  We don't allow these to be
8213      *  sent until we are logged on. Crafty kibitzes have been known to
8214      *  interfere with the login process.
8215      */
8216     if (loggedOn) {
8217         if (!strncmp(message, "tellics ", 8)) {
8218             SendToICS(message + 8);
8219             SendToICS("\n");
8220             return;
8221         }
8222         if (!strncmp(message, "tellicsnoalias ", 15)) {
8223             SendToICS(ics_prefix);
8224             SendToICS(message + 15);
8225             SendToICS("\n");
8226             return;
8227         }
8228         /* The following are for backward compatibility only */
8229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8231             SendToICS(ics_prefix);
8232             SendToICS(message);
8233             SendToICS("\n");
8234             return;
8235         }
8236     }
8237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8238         return;
8239     }
8240     /*
8241      * If the move is illegal, cancel it and redraw the board.
8242      * Also deal with other error cases.  Matching is rather loose
8243      * here to accommodate engines written before the spec.
8244      */
8245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8246         strncmp(message, "Error", 5) == 0) {
8247         if (StrStr(message, "name") ||
8248             StrStr(message, "rating") || StrStr(message, "?") ||
8249             StrStr(message, "result") || StrStr(message, "board") ||
8250             StrStr(message, "bk") || StrStr(message, "computer") ||
8251             StrStr(message, "variant") || StrStr(message, "hint") ||
8252             StrStr(message, "random") || StrStr(message, "depth") ||
8253             StrStr(message, "accepted")) {
8254             return;
8255         }
8256         if (StrStr(message, "protover")) {
8257           /* Program is responding to input, so it's apparently done
8258              initializing, and this error message indicates it is
8259              protocol version 1.  So we don't need to wait any longer
8260              for it to initialize and send feature commands. */
8261           FeatureDone(cps, 1);
8262           cps->protocolVersion = 1;
8263           return;
8264         }
8265         cps->maybeThinking = FALSE;
8266
8267         if (StrStr(message, "draw")) {
8268             /* Program doesn't have "draw" command */
8269             cps->sendDrawOffers = 0;
8270             return;
8271         }
8272         if (cps->sendTime != 1 &&
8273             (StrStr(message, "time") || StrStr(message, "otim"))) {
8274           /* Program apparently doesn't have "time" or "otim" command */
8275           cps->sendTime = 0;
8276           return;
8277         }
8278         if (StrStr(message, "analyze")) {
8279             cps->analysisSupport = FALSE;
8280             cps->analyzing = FALSE;
8281 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8282             EditGameEvent(); // [HGM] try to preserve loaded game
8283             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8284             DisplayError(buf2, 0);
8285             return;
8286         }
8287         if (StrStr(message, "(no matching move)st")) {
8288           /* Special kludge for GNU Chess 4 only */
8289           cps->stKludge = TRUE;
8290           SendTimeControl(cps, movesPerSession, timeControl,
8291                           timeIncrement, appData.searchDepth,
8292                           searchTime);
8293           return;
8294         }
8295         if (StrStr(message, "(no matching move)sd")) {
8296           /* Special kludge for GNU Chess 4 only */
8297           cps->sdKludge = TRUE;
8298           SendTimeControl(cps, movesPerSession, timeControl,
8299                           timeIncrement, appData.searchDepth,
8300                           searchTime);
8301           return;
8302         }
8303         if (!StrStr(message, "llegal")) {
8304             return;
8305         }
8306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8307             gameMode == IcsIdle) return;
8308         if (forwardMostMove <= backwardMostMove) return;
8309         if (pausing) PauseEvent();
8310       if(appData.forceIllegal) {
8311             // [HGM] illegal: machine refused move; force position after move into it
8312           SendToProgram("force\n", cps);
8313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8315                 // when black is to move, while there might be nothing on a2 or black
8316                 // might already have the move. So send the board as if white has the move.
8317                 // But first we must change the stm of the engine, as it refused the last move
8318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8319                 if(WhiteOnMove(forwardMostMove)) {
8320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8321                     SendBoard(cps, forwardMostMove); // kludgeless board
8322                 } else {
8323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8326                 }
8327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8329                  gameMode == TwoMachinesPlay)
8330               SendToProgram("go\n", cps);
8331             return;
8332       } else
8333         if (gameMode == PlayFromGameFile) {
8334             /* Stop reading this game file */
8335             gameMode = EditGame;
8336             ModeHighlight();
8337         }
8338         /* [HGM] illegal-move claim should forfeit game when Xboard */
8339         /* only passes fully legal moves                            */
8340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8342                                 "False illegal-move claim", GE_XBOARD );
8343             return; // do not take back move we tested as valid
8344         }
8345         currentMove = forwardMostMove-1;
8346         DisplayMove(currentMove-1); /* before DisplayMoveError */
8347         SwitchClocks(forwardMostMove-1); // [HGM] race
8348         DisplayBothClocks();
8349         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8350                 parseList[currentMove], _(cps->which));
8351         DisplayMoveError(buf1);
8352         DrawPosition(FALSE, boards[currentMove]);
8353         return;
8354     }
8355     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8356         /* Program has a broken "time" command that
8357            outputs a string not ending in newline.
8358            Don't use it. */
8359         cps->sendTime = 0;
8360     }
8361
8362     /*
8363      * If chess program startup fails, exit with an error message.
8364      * Attempts to recover here are futile.
8365      */
8366     if ((StrStr(message, "unknown host") != NULL)
8367         || (StrStr(message, "No remote directory") != NULL)
8368         || (StrStr(message, "not found") != NULL)
8369         || (StrStr(message, "No such file") != NULL)
8370         || (StrStr(message, "can't alloc") != NULL)
8371         || (StrStr(message, "Permission denied") != NULL)) {
8372
8373         cps->maybeThinking = FALSE;
8374         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8375                 _(cps->which), cps->program, cps->host, message);
8376         RemoveInputSource(cps->isr);
8377         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8378             if(cps == &first) appData.noChessProgram = TRUE;
8379             DisplayError(buf1, 0);
8380         }
8381         return;
8382     }
8383
8384     /*
8385      * Look for hint output
8386      */
8387     if (sscanf(message, "Hint: %s", buf1) == 1) {
8388         if (cps == &first && hintRequested) {
8389             hintRequested = FALSE;
8390             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8391                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8392                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8393                                     PosFlags(forwardMostMove),
8394                                     fromY, fromX, toY, toX, promoChar, buf1);
8395                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8396                 DisplayInformation(buf2);
8397             } else {
8398                 /* Hint move could not be parsed!? */
8399               snprintf(buf2, sizeof(buf2),
8400                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8401                         buf1, _(cps->which));
8402                 DisplayError(buf2, 0);
8403             }
8404         } else {
8405           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8406         }
8407         return;
8408     }
8409
8410     /*
8411      * Ignore other messages if game is not in progress
8412      */
8413     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8414         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8415
8416     /*
8417      * look for win, lose, draw, or draw offer
8418      */
8419     if (strncmp(message, "1-0", 3) == 0) {
8420         char *p, *q, *r = "";
8421         p = strchr(message, '{');
8422         if (p) {
8423             q = strchr(p, '}');
8424             if (q) {
8425                 *q = NULLCHAR;
8426                 r = p + 1;
8427             }
8428         }
8429         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8430         return;
8431     } else if (strncmp(message, "0-1", 3) == 0) {
8432         char *p, *q, *r = "";
8433         p = strchr(message, '{');
8434         if (p) {
8435             q = strchr(p, '}');
8436             if (q) {
8437                 *q = NULLCHAR;
8438                 r = p + 1;
8439             }
8440         }
8441         /* Kludge for Arasan 4.1 bug */
8442         if (strcmp(r, "Black resigns") == 0) {
8443             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8444             return;
8445         }
8446         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8447         return;
8448     } else if (strncmp(message, "1/2", 3) == 0) {
8449         char *p, *q, *r = "";
8450         p = strchr(message, '{');
8451         if (p) {
8452             q = strchr(p, '}');
8453             if (q) {
8454                 *q = NULLCHAR;
8455                 r = p + 1;
8456             }
8457         }
8458
8459         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8460         return;
8461
8462     } else if (strncmp(message, "White resign", 12) == 0) {
8463         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "Black resign", 12) == 0) {
8466         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strncmp(message, "White matches", 13) == 0 ||
8469                strncmp(message, "Black matches", 13) == 0   ) {
8470         /* [HGM] ignore GNUShogi noises */
8471         return;
8472     } else if (strncmp(message, "White", 5) == 0 &&
8473                message[5] != '(' &&
8474                StrStr(message, "Black") == NULL) {
8475         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strncmp(message, "Black", 5) == 0 &&
8478                message[5] != '(') {
8479         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8480         return;
8481     } else if (strcmp(message, "resign") == 0 ||
8482                strcmp(message, "computer resigns") == 0) {
8483         switch (gameMode) {
8484           case MachinePlaysBlack:
8485           case IcsPlayingBlack:
8486             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8487             break;
8488           case MachinePlaysWhite:
8489           case IcsPlayingWhite:
8490             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8491             break;
8492           case TwoMachinesPlay:
8493             if (cps->twoMachinesColor[0] == 'w')
8494               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8495             else
8496               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8497             break;
8498           default:
8499             /* can't happen */
8500             break;
8501         }
8502         return;
8503     } else if (strncmp(message, "opponent mates", 14) == 0) {
8504         switch (gameMode) {
8505           case MachinePlaysBlack:
8506           case IcsPlayingBlack:
8507             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8508             break;
8509           case MachinePlaysWhite:
8510           case IcsPlayingWhite:
8511             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8512             break;
8513           case TwoMachinesPlay:
8514             if (cps->twoMachinesColor[0] == 'w')
8515               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8516             else
8517               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8518             break;
8519           default:
8520             /* can't happen */
8521             break;
8522         }
8523         return;
8524     } else if (strncmp(message, "computer mates", 14) == 0) {
8525         switch (gameMode) {
8526           case MachinePlaysBlack:
8527           case IcsPlayingBlack:
8528             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8529             break;
8530           case MachinePlaysWhite:
8531           case IcsPlayingWhite:
8532             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8533             break;
8534           case TwoMachinesPlay:
8535             if (cps->twoMachinesColor[0] == 'w')
8536               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537             else
8538               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8539             break;
8540           default:
8541             /* can't happen */
8542             break;
8543         }
8544         return;
8545     } else if (strncmp(message, "checkmate", 9) == 0) {
8546         if (WhiteOnMove(forwardMostMove)) {
8547             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8548         } else {
8549             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8550         }
8551         return;
8552     } else if (strstr(message, "Draw") != NULL ||
8553                strstr(message, "game is a draw") != NULL) {
8554         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8555         return;
8556     } else if (strstr(message, "offer") != NULL &&
8557                strstr(message, "draw") != NULL) {
8558 #if ZIPPY
8559         if (appData.zippyPlay && first.initDone) {
8560             /* Relay offer to ICS */
8561             SendToICS(ics_prefix);
8562             SendToICS("draw\n");
8563         }
8564 #endif
8565         cps->offeredDraw = 2; /* valid until this engine moves twice */
8566         if (gameMode == TwoMachinesPlay) {
8567             if (cps->other->offeredDraw) {
8568                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8569             /* [HGM] in two-machine mode we delay relaying draw offer      */
8570             /* until after we also have move, to see if it is really claim */
8571             }
8572         } else if (gameMode == MachinePlaysWhite ||
8573                    gameMode == MachinePlaysBlack) {
8574           if (userOfferedDraw) {
8575             DisplayInformation(_("Machine accepts your draw offer"));
8576             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8577           } else {
8578             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8579           }
8580         }
8581     }
8582
8583
8584     /*
8585      * Look for thinking output
8586      */
8587     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8588           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8589                                 ) {
8590         int plylev, mvleft, mvtot, curscore, time;
8591         char mvname[MOVE_LEN];
8592         u64 nodes; // [DM]
8593         char plyext;
8594         int ignore = FALSE;
8595         int prefixHint = FALSE;
8596         mvname[0] = NULLCHAR;
8597
8598         switch (gameMode) {
8599           case MachinePlaysBlack:
8600           case IcsPlayingBlack:
8601             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8602             break;
8603           case MachinePlaysWhite:
8604           case IcsPlayingWhite:
8605             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8606             break;
8607           case AnalyzeMode:
8608           case AnalyzeFile:
8609             break;
8610           case IcsObserving: /* [DM] icsEngineAnalyze */
8611             if (!appData.icsEngineAnalyze) ignore = TRUE;
8612             break;
8613           case TwoMachinesPlay:
8614             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8615                 ignore = TRUE;
8616             }
8617             break;
8618           default:
8619             ignore = TRUE;
8620             break;
8621         }
8622
8623         if (!ignore) {
8624             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8625             buf1[0] = NULLCHAR;
8626             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8627                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8628
8629                 if (plyext != ' ' && plyext != '\t') {
8630                     time *= 100;
8631                 }
8632
8633                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8634                 if( cps->scoreIsAbsolute &&
8635                     ( gameMode == MachinePlaysBlack ||
8636                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8637                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8638                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8639                      !WhiteOnMove(currentMove)
8640                     ) )
8641                 {
8642                     curscore = -curscore;
8643                 }
8644
8645                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8646
8647                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8648                         char buf[MSG_SIZ];
8649                         FILE *f;
8650                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8651                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8652                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8653                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8654                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8655                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8656                                 fclose(f);
8657                         } else DisplayError("failed writing PV", 0);
8658                 }
8659
8660                 tempStats.depth = plylev;
8661                 tempStats.nodes = nodes;
8662                 tempStats.time = time;
8663                 tempStats.score = curscore;
8664                 tempStats.got_only_move = 0;
8665
8666                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8667                         int ticklen;
8668
8669                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8670                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8671                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8672                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8673                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8674                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8675                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8676                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8677                 }
8678
8679                 /* Buffer overflow protection */
8680                 if (pv[0] != NULLCHAR) {
8681                     if (strlen(pv) >= sizeof(tempStats.movelist)
8682                         && appData.debugMode) {
8683                         fprintf(debugFP,
8684                                 "PV is too long; using the first %u bytes.\n",
8685                                 (unsigned) sizeof(tempStats.movelist) - 1);
8686                     }
8687
8688                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8689                 } else {
8690                     sprintf(tempStats.movelist, " no PV\n");
8691                 }
8692
8693                 if (tempStats.seen_stat) {
8694                     tempStats.ok_to_send = 1;
8695                 }
8696
8697                 if (strchr(tempStats.movelist, '(') != NULL) {
8698                     tempStats.line_is_book = 1;
8699                     tempStats.nr_moves = 0;
8700                     tempStats.moves_left = 0;
8701                 } else {
8702                     tempStats.line_is_book = 0;
8703                 }
8704
8705                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8706                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8707
8708                 SendProgramStatsToFrontend( cps, &tempStats );
8709
8710                 /*
8711                     [AS] Protect the thinkOutput buffer from overflow... this
8712                     is only useful if buf1 hasn't overflowed first!
8713                 */
8714                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8715                          plylev,
8716                          (gameMode == TwoMachinesPlay ?
8717                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8718                          ((double) curscore) / 100.0,
8719                          prefixHint ? lastHint : "",
8720                          prefixHint ? " " : "" );
8721
8722                 if( buf1[0] != NULLCHAR ) {
8723                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8724
8725                     if( strlen(pv) > max_len ) {
8726                         if( appData.debugMode) {
8727                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8728                         }
8729                         pv[max_len+1] = '\0';
8730                     }
8731
8732                     strcat( thinkOutput, pv);
8733                 }
8734
8735                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8736                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8737                     DisplayMove(currentMove - 1);
8738                 }
8739                 return;
8740
8741             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8742                 /* crafty (9.25+) says "(only move) <move>"
8743                  * if there is only 1 legal move
8744                  */
8745                 sscanf(p, "(only move) %s", buf1);
8746                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8747                 sprintf(programStats.movelist, "%s (only move)", buf1);
8748                 programStats.depth = 1;
8749                 programStats.nr_moves = 1;
8750                 programStats.moves_left = 1;
8751                 programStats.nodes = 1;
8752                 programStats.time = 1;
8753                 programStats.got_only_move = 1;
8754
8755                 /* Not really, but we also use this member to
8756                    mean "line isn't going to change" (Crafty
8757                    isn't searching, so stats won't change) */
8758                 programStats.line_is_book = 1;
8759
8760                 SendProgramStatsToFrontend( cps, &programStats );
8761
8762                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8763                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8764                     DisplayMove(currentMove - 1);
8765                 }
8766                 return;
8767             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8768                               &time, &nodes, &plylev, &mvleft,
8769                               &mvtot, mvname) >= 5) {
8770                 /* The stat01: line is from Crafty (9.29+) in response
8771                    to the "." command */
8772                 programStats.seen_stat = 1;
8773                 cps->maybeThinking = TRUE;
8774
8775                 if (programStats.got_only_move || !appData.periodicUpdates)
8776                   return;
8777
8778                 programStats.depth = plylev;
8779                 programStats.time = time;
8780                 programStats.nodes = nodes;
8781                 programStats.moves_left = mvleft;
8782                 programStats.nr_moves = mvtot;
8783                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8784                 programStats.ok_to_send = 1;
8785                 programStats.movelist[0] = '\0';
8786
8787                 SendProgramStatsToFrontend( cps, &programStats );
8788
8789                 return;
8790
8791             } else if (strncmp(message,"++",2) == 0) {
8792                 /* Crafty 9.29+ outputs this */
8793                 programStats.got_fail = 2;
8794                 return;
8795
8796             } else if (strncmp(message,"--",2) == 0) {
8797                 /* Crafty 9.29+ outputs this */
8798                 programStats.got_fail = 1;
8799                 return;
8800
8801             } else if (thinkOutput[0] != NULLCHAR &&
8802                        strncmp(message, "    ", 4) == 0) {
8803                 unsigned message_len;
8804
8805                 p = message;
8806                 while (*p && *p == ' ') p++;
8807
8808                 message_len = strlen( p );
8809
8810                 /* [AS] Avoid buffer overflow */
8811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8812                     strcat(thinkOutput, " ");
8813                     strcat(thinkOutput, p);
8814                 }
8815
8816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8817                     strcat(programStats.movelist, " ");
8818                     strcat(programStats.movelist, p);
8819                 }
8820
8821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8823                     DisplayMove(currentMove - 1);
8824                 }
8825                 return;
8826             }
8827         }
8828         else {
8829             buf1[0] = NULLCHAR;
8830
8831             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8832                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8833             {
8834                 ChessProgramStats cpstats;
8835
8836                 if (plyext != ' ' && plyext != '\t') {
8837                     time *= 100;
8838                 }
8839
8840                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8841                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8842                     curscore = -curscore;
8843                 }
8844
8845                 cpstats.depth = plylev;
8846                 cpstats.nodes = nodes;
8847                 cpstats.time = time;
8848                 cpstats.score = curscore;
8849                 cpstats.got_only_move = 0;
8850                 cpstats.movelist[0] = '\0';
8851
8852                 if (buf1[0] != NULLCHAR) {
8853                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8854                 }
8855
8856                 cpstats.ok_to_send = 0;
8857                 cpstats.line_is_book = 0;
8858                 cpstats.nr_moves = 0;
8859                 cpstats.moves_left = 0;
8860
8861                 SendProgramStatsToFrontend( cps, &cpstats );
8862             }
8863         }
8864     }
8865 }
8866
8867
8868 /* Parse a game score from the character string "game", and
8869    record it as the history of the current game.  The game
8870    score is NOT assumed to start from the standard position.
8871    The display is not updated in any way.
8872    */
8873 void
8874 ParseGameHistory(game)
8875      char *game;
8876 {
8877     ChessMove moveType;
8878     int fromX, fromY, toX, toY, boardIndex;
8879     char promoChar;
8880     char *p, *q;
8881     char buf[MSG_SIZ];
8882
8883     if (appData.debugMode)
8884       fprintf(debugFP, "Parsing game history: %s\n", game);
8885
8886     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8887     gameInfo.site = StrSave(appData.icsHost);
8888     gameInfo.date = PGNDate();
8889     gameInfo.round = StrSave("-");
8890
8891     /* Parse out names of players */
8892     while (*game == ' ') game++;
8893     p = buf;
8894     while (*game != ' ') *p++ = *game++;
8895     *p = NULLCHAR;
8896     gameInfo.white = StrSave(buf);
8897     while (*game == ' ') game++;
8898     p = buf;
8899     while (*game != ' ' && *game != '\n') *p++ = *game++;
8900     *p = NULLCHAR;
8901     gameInfo.black = StrSave(buf);
8902
8903     /* Parse moves */
8904     boardIndex = blackPlaysFirst ? 1 : 0;
8905     yynewstr(game);
8906     for (;;) {
8907         yyboardindex = boardIndex;
8908         moveType = (ChessMove) Myylex();
8909         switch (moveType) {
8910           case IllegalMove:             /* maybe suicide chess, etc. */
8911   if (appData.debugMode) {
8912     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8913     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8914     setbuf(debugFP, NULL);
8915   }
8916           case WhitePromotion:
8917           case BlackPromotion:
8918           case WhiteNonPromotion:
8919           case BlackNonPromotion:
8920           case NormalMove:
8921           case WhiteCapturesEnPassant:
8922           case BlackCapturesEnPassant:
8923           case WhiteKingSideCastle:
8924           case WhiteQueenSideCastle:
8925           case BlackKingSideCastle:
8926           case BlackQueenSideCastle:
8927           case WhiteKingSideCastleWild:
8928           case WhiteQueenSideCastleWild:
8929           case BlackKingSideCastleWild:
8930           case BlackQueenSideCastleWild:
8931           /* PUSH Fabien */
8932           case WhiteHSideCastleFR:
8933           case WhiteASideCastleFR:
8934           case BlackHSideCastleFR:
8935           case BlackASideCastleFR:
8936           /* POP Fabien */
8937             fromX = currentMoveString[0] - AAA;
8938             fromY = currentMoveString[1] - ONE;
8939             toX = currentMoveString[2] - AAA;
8940             toY = currentMoveString[3] - ONE;
8941             promoChar = currentMoveString[4];
8942             break;
8943           case WhiteDrop:
8944           case BlackDrop:
8945             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8946             fromX = moveType == WhiteDrop ?
8947               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8948             (int) CharToPiece(ToLower(currentMoveString[0]));
8949             fromY = DROP_RANK;
8950             toX = currentMoveString[2] - AAA;
8951             toY = currentMoveString[3] - ONE;
8952             promoChar = NULLCHAR;
8953             break;
8954           case AmbiguousMove:
8955             /* bug? */
8956             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8957   if (appData.debugMode) {
8958     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8959     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8960     setbuf(debugFP, NULL);
8961   }
8962             DisplayError(buf, 0);
8963             return;
8964           case ImpossibleMove:
8965             /* bug? */
8966             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8967   if (appData.debugMode) {
8968     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8970     setbuf(debugFP, NULL);
8971   }
8972             DisplayError(buf, 0);
8973             return;
8974           case EndOfFile:
8975             if (boardIndex < backwardMostMove) {
8976                 /* Oops, gap.  How did that happen? */
8977                 DisplayError(_("Gap in move list"), 0);
8978                 return;
8979             }
8980             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8981             if (boardIndex > forwardMostMove) {
8982                 forwardMostMove = boardIndex;
8983             }
8984             return;
8985           case ElapsedTime:
8986             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8987                 strcat(parseList[boardIndex-1], " ");
8988                 strcat(parseList[boardIndex-1], yy_text);
8989             }
8990             continue;
8991           case Comment:
8992           case PGNTag:
8993           case NAG:
8994           default:
8995             /* ignore */
8996             continue;
8997           case WhiteWins:
8998           case BlackWins:
8999           case GameIsDrawn:
9000           case GameUnfinished:
9001             if (gameMode == IcsExamining) {
9002                 if (boardIndex < backwardMostMove) {
9003                     /* Oops, gap.  How did that happen? */
9004                     return;
9005                 }
9006                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9007                 return;
9008             }
9009             gameInfo.result = moveType;
9010             p = strchr(yy_text, '{');
9011             if (p == NULL) p = strchr(yy_text, '(');
9012             if (p == NULL) {
9013                 p = yy_text;
9014                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9015             } else {
9016                 q = strchr(p, *p == '{' ? '}' : ')');
9017                 if (q != NULL) *q = NULLCHAR;
9018                 p++;
9019             }
9020             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9021             gameInfo.resultDetails = StrSave(p);
9022             continue;
9023         }
9024         if (boardIndex >= forwardMostMove &&
9025             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9026             backwardMostMove = blackPlaysFirst ? 1 : 0;
9027             return;
9028         }
9029         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9030                                  fromY, fromX, toY, toX, promoChar,
9031                                  parseList[boardIndex]);
9032         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9033         /* currentMoveString is set as a side-effect of yylex */
9034         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9035         strcat(moveList[boardIndex], "\n");
9036         boardIndex++;
9037         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9038         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9039           case MT_NONE:
9040           case MT_STALEMATE:
9041           default:
9042             break;
9043           case MT_CHECK:
9044             if(gameInfo.variant != VariantShogi)
9045                 strcat(parseList[boardIndex - 1], "+");
9046             break;
9047           case MT_CHECKMATE:
9048           case MT_STAINMATE:
9049             strcat(parseList[boardIndex - 1], "#");
9050             break;
9051         }
9052     }
9053 }
9054
9055
9056 /* Apply a move to the given board  */
9057 void
9058 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9059      int fromX, fromY, toX, toY;
9060      int promoChar;
9061      Board board;
9062 {
9063   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9064   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9065
9066     /* [HGM] compute & store e.p. status and castling rights for new position */
9067     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9068
9069       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9070       oldEP = (signed char)board[EP_STATUS];
9071       board[EP_STATUS] = EP_NONE;
9072
9073   if (fromY == DROP_RANK) {
9074         /* must be first */
9075         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9076             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9077             return;
9078         }
9079         piece = board[toY][toX] = (ChessSquare) fromX;
9080   } else {
9081       int i;
9082
9083       if( board[toY][toX] != EmptySquare )
9084            board[EP_STATUS] = EP_CAPTURE;
9085
9086       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9087            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9088                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9089       } else
9090       if( board[fromY][fromX] == WhitePawn ) {
9091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9092                board[EP_STATUS] = EP_PAWN_MOVE;
9093            if( toY-fromY==2) {
9094                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX < fromX)
9096                       board[EP_STATUS] = toX | berolina;
9097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9098                         gameInfo.variant != VariantBerolina || toX > fromX)
9099                       board[EP_STATUS] = toX;
9100            }
9101       } else
9102       if( board[fromY][fromX] == BlackPawn ) {
9103            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9104                board[EP_STATUS] = EP_PAWN_MOVE;
9105            if( toY-fromY== -2) {
9106                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX < fromX)
9108                       board[EP_STATUS] = toX | berolina;
9109                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9110                         gameInfo.variant != VariantBerolina || toX > fromX)
9111                       board[EP_STATUS] = toX;
9112            }
9113        }
9114
9115        for(i=0; i<nrCastlingRights; i++) {
9116            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9117               board[CASTLING][i] == toX   && castlingRank[i] == toY
9118              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9119        }
9120
9121      if (fromX == toX && fromY == toY) return;
9122
9123      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9124      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9125      if(gameInfo.variant == VariantKnightmate)
9126          king += (int) WhiteUnicorn - (int) WhiteKing;
9127
9128     /* Code added by Tord: */
9129     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9130     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9131         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9132       board[fromY][fromX] = EmptySquare;
9133       board[toY][toX] = EmptySquare;
9134       if((toX > fromX) != (piece == WhiteRook)) {
9135         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9136       } else {
9137         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9138       }
9139     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9140                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == BlackRook)) {
9144         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9145       } else {
9146         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9147       }
9148     /* End of code added by Tord */
9149
9150     } else if (board[fromY][fromX] == king
9151         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9152         && toY == fromY && toX > fromX+1) {
9153         board[fromY][fromX] = EmptySquare;
9154         board[toY][toX] = king;
9155         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9156         board[fromY][BOARD_RGHT-1] = EmptySquare;
9157     } else if (board[fromY][fromX] == king
9158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9159                && toY == fromY && toX < fromX-1) {
9160         board[fromY][fromX] = EmptySquare;
9161         board[toY][toX] = king;
9162         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9163         board[fromY][BOARD_LEFT] = EmptySquare;
9164     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9165                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9166                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9167                ) {
9168         /* white pawn promotion */
9169         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9170         if(gameInfo.variant==VariantBughouse ||
9171            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9172             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9173         board[fromY][fromX] = EmptySquare;
9174     } else if ((fromY >= BOARD_HEIGHT>>1)
9175                && (toX != fromX)
9176                && gameInfo.variant != VariantXiangqi
9177                && gameInfo.variant != VariantBerolina
9178                && (board[fromY][fromX] == WhitePawn)
9179                && (board[toY][toX] == EmptySquare)) {
9180         board[fromY][fromX] = EmptySquare;
9181         board[toY][toX] = WhitePawn;
9182         captured = board[toY - 1][toX];
9183         board[toY - 1][toX] = EmptySquare;
9184     } else if ((fromY == BOARD_HEIGHT-4)
9185                && (toX == fromX)
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         if(oldEP & EP_BEROLIN_A) {
9192                 captured = board[fromY][fromX-1];
9193                 board[fromY][fromX-1] = EmptySquare;
9194         }else{  captured = board[fromY][fromX+1];
9195                 board[fromY][fromX+1] = EmptySquare;
9196         }
9197     } else if (board[fromY][fromX] == king
9198         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9199                && toY == fromY && toX > fromX+1) {
9200         board[fromY][fromX] = EmptySquare;
9201         board[toY][toX] = king;
9202         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9203         board[fromY][BOARD_RGHT-1] = EmptySquare;
9204     } else if (board[fromY][fromX] == king
9205         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9206                && toY == fromY && toX < fromX-1) {
9207         board[fromY][fromX] = EmptySquare;
9208         board[toY][toX] = king;
9209         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9210         board[fromY][BOARD_LEFT] = EmptySquare;
9211     } else if (fromY == 7 && fromX == 3
9212                && board[fromY][fromX] == BlackKing
9213                && toY == 7 && toX == 5) {
9214         board[fromY][fromX] = EmptySquare;
9215         board[toY][toX] = BlackKing;
9216         board[fromY][7] = EmptySquare;
9217         board[toY][4] = BlackRook;
9218     } else if (fromY == 7 && fromX == 3
9219                && board[fromY][fromX] == BlackKing
9220                && toY == 7 && toX == 1) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = BlackKing;
9223         board[fromY][0] = EmptySquare;
9224         board[toY][2] = BlackRook;
9225     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9226                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9227                && toY < promoRank && promoChar
9228                ) {
9229         /* black pawn promotion */
9230         board[toY][toX] = CharToPiece(ToLower(promoChar));
9231         if(gameInfo.variant==VariantBughouse ||
9232            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9233             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9234         board[fromY][fromX] = EmptySquare;
9235     } else if ((fromY < BOARD_HEIGHT>>1)
9236                && (toX != fromX)
9237                && gameInfo.variant != VariantXiangqi
9238                && gameInfo.variant != VariantBerolina
9239                && (board[fromY][fromX] == BlackPawn)
9240                && (board[toY][toX] == EmptySquare)) {
9241         board[fromY][fromX] = EmptySquare;
9242         board[toY][toX] = BlackPawn;
9243         captured = board[toY + 1][toX];
9244         board[toY + 1][toX] = EmptySquare;
9245     } else if ((fromY == 3)
9246                && (toX == fromX)
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         if(oldEP & EP_BEROLIN_A) {
9253                 captured = board[fromY][fromX-1];
9254                 board[fromY][fromX-1] = EmptySquare;
9255         }else{  captured = board[fromY][fromX+1];
9256                 board[fromY][fromX+1] = EmptySquare;
9257         }
9258     } else {
9259         board[toY][toX] = board[fromY][fromX];
9260         board[fromY][fromX] = EmptySquare;
9261     }
9262   }
9263
9264     if (gameInfo.holdingsWidth != 0) {
9265
9266       /* !!A lot more code needs to be written to support holdings  */
9267       /* [HGM] OK, so I have written it. Holdings are stored in the */
9268       /* penultimate board files, so they are automaticlly stored   */
9269       /* in the game history.                                       */
9270       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9271                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9272         /* Delete from holdings, by decreasing count */
9273         /* and erasing image if necessary            */
9274         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9275         if(p < (int) BlackPawn) { /* white drop */
9276              p -= (int)WhitePawn;
9277                  p = PieceToNumber((ChessSquare)p);
9278              if(p >= gameInfo.holdingsSize) p = 0;
9279              if(--board[p][BOARD_WIDTH-2] <= 0)
9280                   board[p][BOARD_WIDTH-1] = EmptySquare;
9281              if((int)board[p][BOARD_WIDTH-2] < 0)
9282                         board[p][BOARD_WIDTH-2] = 0;
9283         } else {                  /* black drop */
9284              p -= (int)BlackPawn;
9285                  p = PieceToNumber((ChessSquare)p);
9286              if(p >= gameInfo.holdingsSize) p = 0;
9287              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9288                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9289              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9290                         board[BOARD_HEIGHT-1-p][1] = 0;
9291         }
9292       }
9293       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9294           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9295         /* [HGM] holdings: Add to holdings, if holdings exist */
9296         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9297                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9298                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9299         }
9300         p = (int) captured;
9301         if (p >= (int) BlackPawn) {
9302           p -= (int)BlackPawn;
9303           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9304                   /* in Shogi restore piece to its original  first */
9305                   captured = (ChessSquare) (DEMOTED captured);
9306                   p = DEMOTED p;
9307           }
9308           p = PieceToNumber((ChessSquare)p);
9309           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9310           board[p][BOARD_WIDTH-2]++;
9311           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9312         } else {
9313           p -= (int)WhitePawn;
9314           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9315                   captured = (ChessSquare) (DEMOTED captured);
9316                   p = DEMOTED p;
9317           }
9318           p = PieceToNumber((ChessSquare)p);
9319           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9320           board[BOARD_HEIGHT-1-p][1]++;
9321           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9322         }
9323       }
9324     } else if (gameInfo.variant == VariantAtomic) {
9325       if (captured != EmptySquare) {
9326         int y, x;
9327         for (y = toY-1; y <= toY+1; y++) {
9328           for (x = toX-1; x <= toX+1; x++) {
9329             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9330                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9331               board[y][x] = EmptySquare;
9332             }
9333           }
9334         }
9335         board[toY][toX] = EmptySquare;
9336       }
9337     }
9338     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9339         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9340     } else
9341     if(promoChar == '+') {
9342         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9343         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9344     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9345         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9346     }
9347     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9348                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9349         // [HGM] superchess: take promotion piece out of holdings
9350         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9351         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9352             if(!--board[k][BOARD_WIDTH-2])
9353                 board[k][BOARD_WIDTH-1] = EmptySquare;
9354         } else {
9355             if(!--board[BOARD_HEIGHT-1-k][1])
9356                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9357         }
9358     }
9359
9360 }
9361
9362 /* Updates forwardMostMove */
9363 void
9364 MakeMove(fromX, fromY, toX, toY, promoChar)
9365      int fromX, fromY, toX, toY;
9366      int promoChar;
9367 {
9368 //    forwardMostMove++; // [HGM] bare: moved downstream
9369
9370     (void) CoordsToAlgebraic(boards[forwardMostMove],
9371                              PosFlags(forwardMostMove),
9372                              fromY, fromX, toY, toX, promoChar,
9373                              parseList[forwardMostMove]);
9374
9375     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9376         int timeLeft; static int lastLoadFlag=0; int king, piece;
9377         piece = boards[forwardMostMove][fromY][fromX];
9378         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9379         if(gameInfo.variant == VariantKnightmate)
9380             king += (int) WhiteUnicorn - (int) WhiteKing;
9381         if(forwardMostMove == 0) {
9382             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9383                 fprintf(serverMoves, "%s;", UserName());
9384             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9385                 fprintf(serverMoves, "%s;", second.tidy);
9386             fprintf(serverMoves, "%s;", first.tidy);
9387             if(gameMode == MachinePlaysWhite)
9388                 fprintf(serverMoves, "%s;", UserName());
9389             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9390                 fprintf(serverMoves, "%s;", second.tidy);
9391         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9392         lastLoadFlag = loadFlag;
9393         // print base move
9394         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9395         // print castling suffix
9396         if( toY == fromY && piece == king ) {
9397             if(toX-fromX > 1)
9398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9399             if(fromX-toX >1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9401         }
9402         // e.p. suffix
9403         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9404              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9405              boards[forwardMostMove][toY][toX] == EmptySquare
9406              && fromX != toX && fromY != toY)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9408         // promotion suffix
9409         if(promoChar != NULLCHAR)
9410                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9411         if(!loadFlag) {
9412                 char buf[MOVE_LEN*2], *p; int len;
9413             fprintf(serverMoves, "/%d/%d",
9414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9416             else                      timeLeft = blackTimeRemaining/1000;
9417             fprintf(serverMoves, "/%d", timeLeft);
9418                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9419                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9420                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9421             fprintf(serverMoves, "/%s", buf);
9422         }
9423         fflush(serverMoves);
9424     }
9425
9426     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9427         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9428       return;
9429     }
9430     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9431     if (commentList[forwardMostMove+1] != NULL) {
9432         free(commentList[forwardMostMove+1]);
9433         commentList[forwardMostMove+1] = NULL;
9434     }
9435     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9437     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9438     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9439     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9440     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9441     gameInfo.result = GameUnfinished;
9442     if (gameInfo.resultDetails != NULL) {
9443         free(gameInfo.resultDetails);
9444         gameInfo.resultDetails = NULL;
9445     }
9446     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9447                               moveList[forwardMostMove - 1]);
9448     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9449       case MT_NONE:
9450       case MT_STALEMATE:
9451       default:
9452         break;
9453       case MT_CHECK:
9454         if(gameInfo.variant != VariantShogi)
9455             strcat(parseList[forwardMostMove - 1], "+");
9456         break;
9457       case MT_CHECKMATE:
9458       case MT_STAINMATE:
9459         strcat(parseList[forwardMostMove - 1], "#");
9460         break;
9461     }
9462     if (appData.debugMode) {
9463         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9464     }
9465
9466 }
9467
9468 /* Updates currentMove if not pausing */
9469 void
9470 ShowMove(fromX, fromY, toX, toY)
9471 {
9472     int instant = (gameMode == PlayFromGameFile) ?
9473         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9474     if(appData.noGUI) return;
9475     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9476         if (!instant) {
9477             if (forwardMostMove == currentMove + 1) {
9478                 AnimateMove(boards[forwardMostMove - 1],
9479                             fromX, fromY, toX, toY);
9480             }
9481             if (appData.highlightLastMove) {
9482                 SetHighlights(fromX, fromY, toX, toY);
9483             }
9484         }
9485         currentMove = forwardMostMove;
9486     }
9487
9488     if (instant) return;
9489
9490     DisplayMove(currentMove - 1);
9491     DrawPosition(FALSE, boards[currentMove]);
9492     DisplayBothClocks();
9493     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9494 }
9495
9496 void SendEgtPath(ChessProgramState *cps)
9497 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9498         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9499
9500         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9501
9502         while(*p) {
9503             char c, *q = name+1, *r, *s;
9504
9505             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9506             while(*p && *p != ',') *q++ = *p++;
9507             *q++ = ':'; *q = 0;
9508             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9509                 strcmp(name, ",nalimov:") == 0 ) {
9510                 // take nalimov path from the menu-changeable option first, if it is defined
9511               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9512                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9513             } else
9514             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9515                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9516                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9517                 s = r = StrStr(s, ":") + 1; // beginning of path info
9518                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9519                 c = *r; *r = 0;             // temporarily null-terminate path info
9520                     *--q = 0;               // strip of trailig ':' from name
9521                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9522                 *r = c;
9523                 SendToProgram(buf,cps);     // send egtbpath command for this format
9524             }
9525             if(*p == ',') p++; // read away comma to position for next format name
9526         }
9527 }
9528
9529 void
9530 InitChessProgram(cps, setup)
9531      ChessProgramState *cps;
9532      int setup; /* [HGM] needed to setup FRC opening position */
9533 {
9534     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9535     if (appData.noChessProgram) return;
9536     hintRequested = FALSE;
9537     bookRequested = FALSE;
9538
9539     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9540     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9541     if(cps->memSize) { /* [HGM] memory */
9542       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9543         SendToProgram(buf, cps);
9544     }
9545     SendEgtPath(cps); /* [HGM] EGT */
9546     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9547       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9548         SendToProgram(buf, cps);
9549     }
9550
9551     SendToProgram(cps->initString, cps);
9552     if (gameInfo.variant != VariantNormal &&
9553         gameInfo.variant != VariantLoadable
9554         /* [HGM] also send variant if board size non-standard */
9555         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9556                                             ) {
9557       char *v = VariantName(gameInfo.variant);
9558       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9559         /* [HGM] in protocol 1 we have to assume all variants valid */
9560         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9561         DisplayFatalError(buf, 0, 1);
9562         return;
9563       }
9564
9565       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9566       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantXiangqi )
9568            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantShogi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9571       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9572            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9573       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9574           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9575            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9576       if( gameInfo.variant == VariantCourier )
9577            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantSuper )
9579            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9580       if( gameInfo.variant == VariantGreat )
9581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantSChess )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9584       if( gameInfo.variant == VariantGrand )
9585            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9586
9587       if(overruled) {
9588         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9589                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9590            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9591            if(StrStr(cps->variants, b) == NULL) {
9592                // specific sized variant not known, check if general sizing allowed
9593                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9594                    if(StrStr(cps->variants, "boardsize") == NULL) {
9595                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9596                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9597                        DisplayFatalError(buf, 0, 1);
9598                        return;
9599                    }
9600                    /* [HGM] here we really should compare with the maximum supported board size */
9601                }
9602            }
9603       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9604       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9605       SendToProgram(buf, cps);
9606     }
9607     currentlyInitializedVariant = gameInfo.variant;
9608
9609     /* [HGM] send opening position in FRC to first engine */
9610     if(setup) {
9611           SendToProgram("force\n", cps);
9612           SendBoard(cps, 0);
9613           /* engine is now in force mode! Set flag to wake it up after first move. */
9614           setboardSpoiledMachineBlack = 1;
9615     }
9616
9617     if (cps->sendICS) {
9618       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9619       SendToProgram(buf, cps);
9620     }
9621     cps->maybeThinking = FALSE;
9622     cps->offeredDraw = 0;
9623     if (!appData.icsActive) {
9624         SendTimeControl(cps, movesPerSession, timeControl,
9625                         timeIncrement, appData.searchDepth,
9626                         searchTime);
9627     }
9628     if (appData.showThinking
9629         // [HGM] thinking: four options require thinking output to be sent
9630         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9631                                 ) {
9632         SendToProgram("post\n", cps);
9633     }
9634     SendToProgram("hard\n", cps);
9635     if (!appData.ponderNextMove) {
9636         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9637            it without being sure what state we are in first.  "hard"
9638            is not a toggle, so that one is OK.
9639          */
9640         SendToProgram("easy\n", cps);
9641     }
9642     if (cps->usePing) {
9643       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9644       SendToProgram(buf, cps);
9645     }
9646     cps->initDone = TRUE;
9647     ClearEngineOutputPane(cps == &second);
9648 }
9649
9650
9651 void
9652 StartChessProgram(cps)
9653      ChessProgramState *cps;
9654 {
9655     char buf[MSG_SIZ];
9656     int err;
9657
9658     if (appData.noChessProgram) return;
9659     cps->initDone = FALSE;
9660
9661     if (strcmp(cps->host, "localhost") == 0) {
9662         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9663     } else if (*appData.remoteShell == NULLCHAR) {
9664         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9665     } else {
9666         if (*appData.remoteUser == NULLCHAR) {
9667           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9668                     cps->program);
9669         } else {
9670           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9671                     cps->host, appData.remoteUser, cps->program);
9672         }
9673         err = StartChildProcess(buf, "", &cps->pr);
9674     }
9675
9676     if (err != 0) {
9677       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9678         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9679         if(cps != &first) return;
9680         appData.noChessProgram = TRUE;
9681         ThawUI();
9682         SetNCPMode();
9683 //      DisplayFatalError(buf, err, 1);
9684 //      cps->pr = NoProc;
9685 //      cps->isr = NULL;
9686         return;
9687     }
9688
9689     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9690     if (cps->protocolVersion > 1) {
9691       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9692       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9693       cps->comboCnt = 0;  //                and values of combo boxes
9694       SendToProgram(buf, cps);
9695     } else {
9696       SendToProgram("xboard\n", cps);
9697     }
9698 }
9699
9700 void
9701 TwoMachinesEventIfReady P((void))
9702 {
9703   static int curMess = 0;
9704   if (first.lastPing != first.lastPong) {
9705     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9706     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9707     return;
9708   }
9709   if (second.lastPing != second.lastPong) {
9710     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9711     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9712     return;
9713   }
9714   DisplayMessage("", ""); curMess = 0;
9715   ThawUI();
9716   TwoMachinesEvent();
9717 }
9718
9719 char *
9720 MakeName(char *template)
9721 {
9722     time_t clock;
9723     struct tm *tm;
9724     static char buf[MSG_SIZ];
9725     char *p = buf;
9726     int i;
9727
9728     clock = time((time_t *)NULL);
9729     tm = localtime(&clock);
9730
9731     while(*p++ = *template++) if(p[-1] == '%') {
9732         switch(*template++) {
9733           case 0:   *p = 0; return buf;
9734           case 'Y': i = tm->tm_year+1900; break;
9735           case 'y': i = tm->tm_year-100; break;
9736           case 'M': i = tm->tm_mon+1; break;
9737           case 'd': i = tm->tm_mday; break;
9738           case 'h': i = tm->tm_hour; break;
9739           case 'm': i = tm->tm_min; break;
9740           case 's': i = tm->tm_sec; break;
9741           default:  i = 0;
9742         }
9743         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9744     }
9745     return buf;
9746 }
9747
9748 int
9749 CountPlayers(char *p)
9750 {
9751     int n = 0;
9752     while(p = strchr(p, '\n')) p++, n++; // count participants
9753     return n;
9754 }
9755
9756 FILE *
9757 WriteTourneyFile(char *results, FILE *f)
9758 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9759     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9760     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9761         // create a file with tournament description
9762         fprintf(f, "-participants {%s}\n", appData.participants);
9763         fprintf(f, "-seedBase %d\n", appData.seedBase);
9764         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9765         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9766         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9767         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9768         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9769         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9770         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9771         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9772         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9773         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9774         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9775         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9776         if(searchTime > 0)
9777                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9778         else {
9779                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9780                 fprintf(f, "-tc %s\n", appData.timeControl);
9781                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9782         }
9783         fprintf(f, "-results \"%s\"\n", results);
9784     }
9785     return f;
9786 }
9787
9788 #define MAXENGINES 1000
9789 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9790
9791 void Substitute(char *participants, int expunge)
9792 {
9793     int i, changed, changes=0, nPlayers=0;
9794     char *p, *q, *r, buf[MSG_SIZ];
9795     if(participants == NULL) return;
9796     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9797     r = p = participants; q = appData.participants;
9798     while(*p && *p == *q) {
9799         if(*p == '\n') r = p+1, nPlayers++;
9800         p++; q++;
9801     }
9802     if(*p) { // difference
9803         while(*p && *p++ != '\n');
9804         while(*q && *q++ != '\n');
9805       changed = nPlayers;
9806         changes = 1 + (strcmp(p, q) != 0);
9807     }
9808     if(changes == 1) { // a single engine mnemonic was changed
9809         q = r; while(*q) nPlayers += (*q++ == '\n');
9810         p = buf; while(*r && (*p = *r++) != '\n') p++;
9811         *p = NULLCHAR;
9812         NamesToList(firstChessProgramNames, command, mnemonic);
9813         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9814         if(mnemonic[i]) { // The substitute is valid
9815             FILE *f;
9816             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9817                 flock(fileno(f), LOCK_EX);
9818                 ParseArgsFromFile(f);
9819                 fseek(f, 0, SEEK_SET);
9820                 FREE(appData.participants); appData.participants = participants;
9821                 if(expunge) { // erase results of replaced engine
9822                     int len = strlen(appData.results), w, b, dummy;
9823                     for(i=0; i<len; i++) {
9824                         Pairing(i, nPlayers, &w, &b, &dummy);
9825                         if((w == changed || b == changed) && appData.results[i] == '*') {
9826                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9827                             fclose(f);
9828                             return;
9829                         }
9830                     }
9831                     for(i=0; i<len; i++) {
9832                         Pairing(i, nPlayers, &w, &b, &dummy);
9833                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9834                     }
9835                 }
9836                 WriteTourneyFile(appData.results, f);
9837                 fclose(f); // release lock
9838                 return;
9839             }
9840         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9841     }
9842     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9843     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9844     free(participants);
9845     return;
9846 }
9847
9848 int
9849 CreateTourney(char *name)
9850 {
9851         FILE *f;
9852         if(matchMode && strcmp(name, appData.tourneyFile)) {
9853              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9854         }
9855         if(name[0] == NULLCHAR) {
9856             if(appData.participants[0])
9857                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9858             return 0;
9859         }
9860         f = fopen(name, "r");
9861         if(f) { // file exists
9862             ASSIGN(appData.tourneyFile, name);
9863             ParseArgsFromFile(f); // parse it
9864         } else {
9865             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9866             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9867                 DisplayError(_("Not enough participants"), 0);
9868                 return 0;
9869             }
9870             ASSIGN(appData.tourneyFile, name);
9871             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9872             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9873         }
9874         fclose(f);
9875         appData.noChessProgram = FALSE;
9876         appData.clockMode = TRUE;
9877         SetGNUMode();
9878         return 1;
9879 }
9880
9881 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9882 {
9883     char buf[MSG_SIZ], *p, *q;
9884     int i=1;
9885     while(*names) {
9886         p = names; q = buf;
9887         while(*p && *p != '\n') *q++ = *p++;
9888         *q = 0;
9889         if(engineList[i]) free(engineList[i]);
9890         engineList[i] = strdup(buf);
9891         if(*p == '\n') p++;
9892         TidyProgramName(engineList[i], "localhost", buf);
9893         if(engineMnemonic[i]) free(engineMnemonic[i]);
9894         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9895             strcat(buf, " (");
9896             sscanf(q + 8, "%s", buf + strlen(buf));
9897             strcat(buf, ")");
9898         }
9899         engineMnemonic[i] = strdup(buf);
9900         names = p; i++;
9901       if(i > MAXENGINES - 2) break;
9902     }
9903     engineList[i] = engineMnemonic[i] = NULL;
9904 }
9905
9906 // following implemented as macro to avoid type limitations
9907 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9908
9909 void SwapEngines(int n)
9910 {   // swap settings for first engine and other engine (so far only some selected options)
9911     int h;
9912     char *p;
9913     if(n == 0) return;
9914     SWAP(directory, p)
9915     SWAP(chessProgram, p)
9916     SWAP(isUCI, h)
9917     SWAP(hasOwnBookUCI, h)
9918     SWAP(protocolVersion, h)
9919     SWAP(reuse, h)
9920     SWAP(scoreIsAbsolute, h)
9921     SWAP(timeOdds, h)
9922     SWAP(logo, p)
9923     SWAP(pgnName, p)
9924     SWAP(pvSAN, h)
9925 }
9926
9927 void
9928 SetPlayer(int player)
9929 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9930     int i;
9931     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9932     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9933     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9934     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9935     if(mnemonic[i]) {
9936         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9937         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9938         appData.firstHasOwnBookUCI = !appData.defNoBook;
9939         ParseArgsFromString(buf);
9940     }
9941     free(engineName);
9942 }
9943
9944 int
9945 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9946 {   // determine players from game number
9947     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9948
9949     if(appData.tourneyType == 0) {
9950         roundsPerCycle = (nPlayers - 1) | 1;
9951         pairingsPerRound = nPlayers / 2;
9952     } else if(appData.tourneyType > 0) {
9953         roundsPerCycle = nPlayers - appData.tourneyType;
9954         pairingsPerRound = appData.tourneyType;
9955     }
9956     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9957     gamesPerCycle = gamesPerRound * roundsPerCycle;
9958     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9959     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9960     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9961     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9962     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9963     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9964
9965     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9966     if(appData.roundSync) *syncInterval = gamesPerRound;
9967
9968     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9969
9970     if(appData.tourneyType == 0) {
9971         if(curPairing == (nPlayers-1)/2 ) {
9972             *whitePlayer = curRound;
9973             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9974         } else {
9975             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9976             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9977             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9978             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9979         }
9980     } else if(appData.tourneyType > 0) {
9981         *whitePlayer = curPairing;
9982         *blackPlayer = curRound + appData.tourneyType;
9983     }
9984
9985     // take care of white/black alternation per round. 
9986     // For cycles and games this is already taken care of by default, derived from matchGame!
9987     return curRound & 1;
9988 }
9989
9990 int
9991 NextTourneyGame(int nr, int *swapColors)
9992 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9993     char *p, *q;
9994     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9995     FILE *tf;
9996     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9997     tf = fopen(appData.tourneyFile, "r");
9998     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9999     ParseArgsFromFile(tf); fclose(tf);
10000     InitTimeControls(); // TC might be altered from tourney file
10001
10002     nPlayers = CountPlayers(appData.participants); // count participants
10003     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10004     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10005
10006     if(syncInterval) {
10007         p = q = appData.results;
10008         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10009         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10010             DisplayMessage(_("Waiting for other game(s)"),"");
10011             waitingForGame = TRUE;
10012             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10013             return 0;
10014         }
10015         waitingForGame = FALSE;
10016     }
10017
10018     if(appData.tourneyType < 0) {
10019         if(nr>=0 && !pairingReceived) {
10020             char buf[1<<16];
10021             if(pairing.pr == NoProc) {
10022                 if(!appData.pairingEngine[0]) {
10023                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10024                     return 0;
10025                 }
10026                 StartChessProgram(&pairing); // starts the pairing engine
10027             }
10028             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10029             SendToProgram(buf, &pairing);
10030             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10031             SendToProgram(buf, &pairing);
10032             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10033         }
10034         pairingReceived = 0;                              // ... so we continue here 
10035         *swapColors = 0;
10036         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10037         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10038         matchGame = 1; roundNr = nr / syncInterval + 1;
10039     }
10040
10041     if(first.pr != NoProc) return 1; // engines already loaded
10042
10043     // redefine engines, engine dir, etc.
10044     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10045     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10046     SwapEngines(1);
10047     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10048     SwapEngines(1);         // and make that valid for second engine by swapping
10049     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10050     InitEngine(&second, 1);
10051     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10052     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10053     return 1;
10054 }
10055
10056 void
10057 NextMatchGame()
10058 {   // performs game initialization that does not invoke engines, and then tries to start the game
10059     int res, firstWhite, swapColors = 0;
10060     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10061     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10062     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10063     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10064     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10065     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10066     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10067     Reset(FALSE, first.pr != NoProc);
10068     res = LoadGameOrPosition(matchGame); // setup game
10069     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10070     if(!res) return; // abort when bad game/pos file
10071     TwoMachinesEvent();
10072 }
10073
10074 void UserAdjudicationEvent( int result )
10075 {
10076     ChessMove gameResult = GameIsDrawn;
10077
10078     if( result > 0 ) {
10079         gameResult = WhiteWins;
10080     }
10081     else if( result < 0 ) {
10082         gameResult = BlackWins;
10083     }
10084
10085     if( gameMode == TwoMachinesPlay ) {
10086         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10087     }
10088 }
10089
10090
10091 // [HGM] save: calculate checksum of game to make games easily identifiable
10092 int StringCheckSum(char *s)
10093 {
10094         int i = 0;
10095         if(s==NULL) return 0;
10096         while(*s) i = i*259 + *s++;
10097         return i;
10098 }
10099
10100 int GameCheckSum()
10101 {
10102         int i, sum=0;
10103         for(i=backwardMostMove; i<forwardMostMove; i++) {
10104                 sum += pvInfoList[i].depth;
10105                 sum += StringCheckSum(parseList[i]);
10106                 sum += StringCheckSum(commentList[i]);
10107                 sum *= 261;
10108         }
10109         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10110         return sum + StringCheckSum(commentList[i]);
10111 } // end of save patch
10112
10113 void
10114 GameEnds(result, resultDetails, whosays)
10115      ChessMove result;
10116      char *resultDetails;
10117      int whosays;
10118 {
10119     GameMode nextGameMode;
10120     int isIcsGame;
10121     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10122
10123     if(endingGame) return; /* [HGM] crash: forbid recursion */
10124     endingGame = 1;
10125     if(twoBoards) { // [HGM] dual: switch back to one board
10126         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10127         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10128     }
10129     if (appData.debugMode) {
10130       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10131               result, resultDetails ? resultDetails : "(null)", whosays);
10132     }
10133
10134     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10135
10136     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10137         /* If we are playing on ICS, the server decides when the
10138            game is over, but the engine can offer to draw, claim
10139            a draw, or resign.
10140          */
10141 #if ZIPPY
10142         if (appData.zippyPlay && first.initDone) {
10143             if (result == GameIsDrawn) {
10144                 /* In case draw still needs to be claimed */
10145                 SendToICS(ics_prefix);
10146                 SendToICS("draw\n");
10147             } else if (StrCaseStr(resultDetails, "resign")) {
10148                 SendToICS(ics_prefix);
10149                 SendToICS("resign\n");
10150             }
10151         }
10152 #endif
10153         endingGame = 0; /* [HGM] crash */
10154         return;
10155     }
10156
10157     /* If we're loading the game from a file, stop */
10158     if (whosays == GE_FILE) {
10159       (void) StopLoadGameTimer();
10160       gameFileFP = NULL;
10161     }
10162
10163     /* Cancel draw offers */
10164     first.offeredDraw = second.offeredDraw = 0;
10165
10166     /* If this is an ICS game, only ICS can really say it's done;
10167        if not, anyone can. */
10168     isIcsGame = (gameMode == IcsPlayingWhite ||
10169                  gameMode == IcsPlayingBlack ||
10170                  gameMode == IcsObserving    ||
10171                  gameMode == IcsExamining);
10172
10173     if (!isIcsGame || whosays == GE_ICS) {
10174         /* OK -- not an ICS game, or ICS said it was done */
10175         StopClocks();
10176         if (!isIcsGame && !appData.noChessProgram)
10177           SetUserThinkingEnables();
10178
10179         /* [HGM] if a machine claims the game end we verify this claim */
10180         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10181             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10182                 char claimer;
10183                 ChessMove trueResult = (ChessMove) -1;
10184
10185                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10186                                             first.twoMachinesColor[0] :
10187                                             second.twoMachinesColor[0] ;
10188
10189                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10190                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10191                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10192                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10193                 } else
10194                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10195                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10196                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10197                 } else
10198                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10199                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10200                 }
10201
10202                 // now verify win claims, but not in drop games, as we don't understand those yet
10203                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10204                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10205                     (result == WhiteWins && claimer == 'w' ||
10206                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10207                       if (appData.debugMode) {
10208                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10209                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10210                       }
10211                       if(result != trueResult) {
10212                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10213                               result = claimer == 'w' ? BlackWins : WhiteWins;
10214                               resultDetails = buf;
10215                       }
10216                 } else
10217                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10218                     && (forwardMostMove <= backwardMostMove ||
10219                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10220                         (claimer=='b')==(forwardMostMove&1))
10221                                                                                   ) {
10222                       /* [HGM] verify: draws that were not flagged are false claims */
10223                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10224                       result = claimer == 'w' ? BlackWins : WhiteWins;
10225                       resultDetails = buf;
10226                 }
10227                 /* (Claiming a loss is accepted no questions asked!) */
10228             }
10229             /* [HGM] bare: don't allow bare King to win */
10230             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10231                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10232                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10233                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10234                && result != GameIsDrawn)
10235             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10236                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10237                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10238                         if(p >= 0 && p <= (int)WhiteKing) k++;
10239                 }
10240                 if (appData.debugMode) {
10241                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10242                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10243                 }
10244                 if(k <= 1) {
10245                         result = GameIsDrawn;
10246                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10247                         resultDetails = buf;
10248                 }
10249             }
10250         }
10251
10252
10253         if(serverMoves != NULL && !loadFlag) { char c = '=';
10254             if(result==WhiteWins) c = '+';
10255             if(result==BlackWins) c = '-';
10256             if(resultDetails != NULL)
10257                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10258         }
10259         if (resultDetails != NULL) {
10260             gameInfo.result = result;
10261             gameInfo.resultDetails = StrSave(resultDetails);
10262
10263             /* display last move only if game was not loaded from file */
10264             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10265                 DisplayMove(currentMove - 1);
10266
10267             if (forwardMostMove != 0) {
10268                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10269                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10270                                                                 ) {
10271                     if (*appData.saveGameFile != NULLCHAR) {
10272                         SaveGameToFile(appData.saveGameFile, TRUE);
10273                     } else if (appData.autoSaveGames) {
10274                         AutoSaveGame();
10275                     }
10276                     if (*appData.savePositionFile != NULLCHAR) {
10277                         SavePositionToFile(appData.savePositionFile);
10278                     }
10279                 }
10280             }
10281
10282             /* Tell program how game ended in case it is learning */
10283             /* [HGM] Moved this to after saving the PGN, just in case */
10284             /* engine died and we got here through time loss. In that */
10285             /* case we will get a fatal error writing the pipe, which */
10286             /* would otherwise lose us the PGN.                       */
10287             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10288             /* output during GameEnds should never be fatal anymore   */
10289             if (gameMode == MachinePlaysWhite ||
10290                 gameMode == MachinePlaysBlack ||
10291                 gameMode == TwoMachinesPlay ||
10292                 gameMode == IcsPlayingWhite ||
10293                 gameMode == IcsPlayingBlack ||
10294                 gameMode == BeginningOfGame) {
10295                 char buf[MSG_SIZ];
10296                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10297                         resultDetails);
10298                 if (first.pr != NoProc) {
10299                     SendToProgram(buf, &first);
10300                 }
10301                 if (second.pr != NoProc &&
10302                     gameMode == TwoMachinesPlay) {
10303                     SendToProgram(buf, &second);
10304                 }
10305             }
10306         }
10307
10308         if (appData.icsActive) {
10309             if (appData.quietPlay &&
10310                 (gameMode == IcsPlayingWhite ||
10311                  gameMode == IcsPlayingBlack)) {
10312                 SendToICS(ics_prefix);
10313                 SendToICS("set shout 1\n");
10314             }
10315             nextGameMode = IcsIdle;
10316             ics_user_moved = FALSE;
10317             /* clean up premove.  It's ugly when the game has ended and the
10318              * premove highlights are still on the board.
10319              */
10320             if (gotPremove) {
10321               gotPremove = FALSE;
10322               ClearPremoveHighlights();
10323               DrawPosition(FALSE, boards[currentMove]);
10324             }
10325             if (whosays == GE_ICS) {
10326                 switch (result) {
10327                 case WhiteWins:
10328                     if (gameMode == IcsPlayingWhite)
10329                         PlayIcsWinSound();
10330                     else if(gameMode == IcsPlayingBlack)
10331                         PlayIcsLossSound();
10332                     break;
10333                 case BlackWins:
10334                     if (gameMode == IcsPlayingBlack)
10335                         PlayIcsWinSound();
10336                     else if(gameMode == IcsPlayingWhite)
10337                         PlayIcsLossSound();
10338                     break;
10339                 case GameIsDrawn:
10340                     PlayIcsDrawSound();
10341                     break;
10342                 default:
10343                     PlayIcsUnfinishedSound();
10344                 }
10345             }
10346         } else if (gameMode == EditGame ||
10347                    gameMode == PlayFromGameFile ||
10348                    gameMode == AnalyzeMode ||
10349                    gameMode == AnalyzeFile) {
10350             nextGameMode = gameMode;
10351         } else {
10352             nextGameMode = EndOfGame;
10353         }
10354         pausing = FALSE;
10355         ModeHighlight();
10356     } else {
10357         nextGameMode = gameMode;
10358     }
10359
10360     if (appData.noChessProgram) {
10361         gameMode = nextGameMode;
10362         ModeHighlight();
10363         endingGame = 0; /* [HGM] crash */
10364         return;
10365     }
10366
10367     if (first.reuse) {
10368         /* Put first chess program into idle state */
10369         if (first.pr != NoProc &&
10370             (gameMode == MachinePlaysWhite ||
10371              gameMode == MachinePlaysBlack ||
10372              gameMode == TwoMachinesPlay ||
10373              gameMode == IcsPlayingWhite ||
10374              gameMode == IcsPlayingBlack ||
10375              gameMode == BeginningOfGame)) {
10376             SendToProgram("force\n", &first);
10377             if (first.usePing) {
10378               char buf[MSG_SIZ];
10379               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10380               SendToProgram(buf, &first);
10381             }
10382         }
10383     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10384         /* Kill off first chess program */
10385         if (first.isr != NULL)
10386           RemoveInputSource(first.isr);
10387         first.isr = NULL;
10388
10389         if (first.pr != NoProc) {
10390             ExitAnalyzeMode();
10391             DoSleep( appData.delayBeforeQuit );
10392             SendToProgram("quit\n", &first);
10393             DoSleep( appData.delayAfterQuit );
10394             DestroyChildProcess(first.pr, first.useSigterm);
10395         }
10396         first.pr = NoProc;
10397     }
10398     if (second.reuse) {
10399         /* Put second chess program into idle state */
10400         if (second.pr != NoProc &&
10401             gameMode == TwoMachinesPlay) {
10402             SendToProgram("force\n", &second);
10403             if (second.usePing) {
10404               char buf[MSG_SIZ];
10405               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10406               SendToProgram(buf, &second);
10407             }
10408         }
10409     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10410         /* Kill off second chess program */
10411         if (second.isr != NULL)
10412           RemoveInputSource(second.isr);
10413         second.isr = NULL;
10414
10415         if (second.pr != NoProc) {
10416             DoSleep( appData.delayBeforeQuit );
10417             SendToProgram("quit\n", &second);
10418             DoSleep( appData.delayAfterQuit );
10419             DestroyChildProcess(second.pr, second.useSigterm);
10420         }
10421         second.pr = NoProc;
10422     }
10423
10424     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10425         char resChar = '=';
10426         switch (result) {
10427         case WhiteWins:
10428           resChar = '+';
10429           if (first.twoMachinesColor[0] == 'w') {
10430             first.matchWins++;
10431           } else {
10432             second.matchWins++;
10433           }
10434           break;
10435         case BlackWins:
10436           resChar = '-';
10437           if (first.twoMachinesColor[0] == 'b') {
10438             first.matchWins++;
10439           } else {
10440             second.matchWins++;
10441           }
10442           break;
10443         case GameUnfinished:
10444           resChar = ' ';
10445         default:
10446           break;
10447         }
10448
10449         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10450         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10451             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10452             ReserveGame(nextGame, resChar); // sets nextGame
10453             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10454             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10455         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10456
10457         if (nextGame <= appData.matchGames && !abortMatch) {
10458             gameMode = nextGameMode;
10459             matchGame = nextGame; // this will be overruled in tourney mode!
10460             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10461             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10462             endingGame = 0; /* [HGM] crash */
10463             return;
10464         } else {
10465             gameMode = nextGameMode;
10466             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10467                      first.tidy, second.tidy,
10468                      first.matchWins, second.matchWins,
10469                      appData.matchGames - (first.matchWins + second.matchWins));
10470             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10471             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10472             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10473                 first.twoMachinesColor = "black\n";
10474                 second.twoMachinesColor = "white\n";
10475             } else {
10476                 first.twoMachinesColor = "white\n";
10477                 second.twoMachinesColor = "black\n";
10478             }
10479         }
10480     }
10481     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10482         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10483       ExitAnalyzeMode();
10484     gameMode = nextGameMode;
10485     ModeHighlight();
10486     endingGame = 0;  /* [HGM] crash */
10487     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10488         if(matchMode == TRUE) { // match through command line: exit with or without popup
10489             if(ranking) {
10490                 ToNrEvent(forwardMostMove);
10491                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10492                 else ExitEvent(0);
10493             } else DisplayFatalError(buf, 0, 0);
10494         } else { // match through menu; just stop, with or without popup
10495             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10496             ModeHighlight();
10497             if(ranking){
10498                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10499             } else DisplayNote(buf);
10500       }
10501       if(ranking) free(ranking);
10502     }
10503 }
10504
10505 /* Assumes program was just initialized (initString sent).
10506    Leaves program in force mode. */
10507 void
10508 FeedMovesToProgram(cps, upto)
10509      ChessProgramState *cps;
10510      int upto;
10511 {
10512     int i;
10513
10514     if (appData.debugMode)
10515       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10516               startedFromSetupPosition ? "position and " : "",
10517               backwardMostMove, upto, cps->which);
10518     if(currentlyInitializedVariant != gameInfo.variant) {
10519       char buf[MSG_SIZ];
10520         // [HGM] variantswitch: make engine aware of new variant
10521         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10522                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10523         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10524         SendToProgram(buf, cps);
10525         currentlyInitializedVariant = gameInfo.variant;
10526     }
10527     SendToProgram("force\n", cps);
10528     if (startedFromSetupPosition) {
10529         SendBoard(cps, backwardMostMove);
10530     if (appData.debugMode) {
10531         fprintf(debugFP, "feedMoves\n");
10532     }
10533     }
10534     for (i = backwardMostMove; i < upto; i++) {
10535         SendMoveToProgram(i, cps);
10536     }
10537 }
10538
10539
10540 int
10541 ResurrectChessProgram()
10542 {
10543      /* The chess program may have exited.
10544         If so, restart it and feed it all the moves made so far. */
10545     static int doInit = 0;
10546
10547     if (appData.noChessProgram) return 1;
10548
10549     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10550         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10551         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10552         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10553     } else {
10554         if (first.pr != NoProc) return 1;
10555         StartChessProgram(&first);
10556     }
10557     InitChessProgram(&first, FALSE);
10558     FeedMovesToProgram(&first, currentMove);
10559
10560     if (!first.sendTime) {
10561         /* can't tell gnuchess what its clock should read,
10562            so we bow to its notion. */
10563         ResetClocks();
10564         timeRemaining[0][currentMove] = whiteTimeRemaining;
10565         timeRemaining[1][currentMove] = blackTimeRemaining;
10566     }
10567
10568     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10569                 appData.icsEngineAnalyze) && first.analysisSupport) {
10570       SendToProgram("analyze\n", &first);
10571       first.analyzing = TRUE;
10572     }
10573     return 1;
10574 }
10575
10576 /*
10577  * Button procedures
10578  */
10579 void
10580 Reset(redraw, init)
10581      int redraw, init;
10582 {
10583     int i;
10584
10585     if (appData.debugMode) {
10586         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10587                 redraw, init, gameMode);
10588     }
10589     CleanupTail(); // [HGM] vari: delete any stored variations
10590     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10591     pausing = pauseExamInvalid = FALSE;
10592     startedFromSetupPosition = blackPlaysFirst = FALSE;
10593     firstMove = TRUE;
10594     whiteFlag = blackFlag = FALSE;
10595     userOfferedDraw = FALSE;
10596     hintRequested = bookRequested = FALSE;
10597     first.maybeThinking = FALSE;
10598     second.maybeThinking = FALSE;
10599     first.bookSuspend = FALSE; // [HGM] book
10600     second.bookSuspend = FALSE;
10601     thinkOutput[0] = NULLCHAR;
10602     lastHint[0] = NULLCHAR;
10603     ClearGameInfo(&gameInfo);
10604     gameInfo.variant = StringToVariant(appData.variant);
10605     ics_user_moved = ics_clock_paused = FALSE;
10606     ics_getting_history = H_FALSE;
10607     ics_gamenum = -1;
10608     white_holding[0] = black_holding[0] = NULLCHAR;
10609     ClearProgramStats();
10610     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10611
10612     ResetFrontEnd();
10613     ClearHighlights();
10614     flipView = appData.flipView;
10615     ClearPremoveHighlights();
10616     gotPremove = FALSE;
10617     alarmSounded = FALSE;
10618
10619     GameEnds(EndOfFile, NULL, GE_PLAYER);
10620     if(appData.serverMovesName != NULL) {
10621         /* [HGM] prepare to make moves file for broadcasting */
10622         clock_t t = clock();
10623         if(serverMoves != NULL) fclose(serverMoves);
10624         serverMoves = fopen(appData.serverMovesName, "r");
10625         if(serverMoves != NULL) {
10626             fclose(serverMoves);
10627             /* delay 15 sec before overwriting, so all clients can see end */
10628             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10629         }
10630         serverMoves = fopen(appData.serverMovesName, "w");
10631     }
10632
10633     ExitAnalyzeMode();
10634     gameMode = BeginningOfGame;
10635     ModeHighlight();
10636     if(appData.icsActive) gameInfo.variant = VariantNormal;
10637     currentMove = forwardMostMove = backwardMostMove = 0;
10638     InitPosition(redraw);
10639     for (i = 0; i < MAX_MOVES; i++) {
10640         if (commentList[i] != NULL) {
10641             free(commentList[i]);
10642             commentList[i] = NULL;
10643         }
10644     }
10645     ResetClocks();
10646     timeRemaining[0][0] = whiteTimeRemaining;
10647     timeRemaining[1][0] = blackTimeRemaining;
10648
10649     if (first.pr == NULL) {
10650         StartChessProgram(&first);
10651     }
10652     if (init) {
10653             InitChessProgram(&first, startedFromSetupPosition);
10654     }
10655     DisplayTitle("");
10656     DisplayMessage("", "");
10657     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10658     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10659 }
10660
10661 void
10662 AutoPlayGameLoop()
10663 {
10664     for (;;) {
10665         if (!AutoPlayOneMove())
10666           return;
10667         if (matchMode || appData.timeDelay == 0)
10668           continue;
10669         if (appData.timeDelay < 0)
10670           return;
10671         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10672         break;
10673     }
10674 }
10675
10676
10677 int
10678 AutoPlayOneMove()
10679 {
10680     int fromX, fromY, toX, toY;
10681
10682     if (appData.debugMode) {
10683       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10684     }
10685
10686     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10687       return FALSE;
10688
10689     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10690       pvInfoList[currentMove].depth = programStats.depth;
10691       pvInfoList[currentMove].score = programStats.score;
10692       pvInfoList[currentMove].time  = 0;
10693       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10694     }
10695
10696     if (currentMove >= forwardMostMove) {
10697       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10698 //      gameMode = EndOfGame;
10699 //      ModeHighlight();
10700
10701       /* [AS] Clear current move marker at the end of a game */
10702       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10703
10704       return FALSE;
10705     }
10706
10707     toX = moveList[currentMove][2] - AAA;
10708     toY = moveList[currentMove][3] - ONE;
10709
10710     if (moveList[currentMove][1] == '@') {
10711         if (appData.highlightLastMove) {
10712             SetHighlights(-1, -1, toX, toY);
10713         }
10714     } else {
10715         fromX = moveList[currentMove][0] - AAA;
10716         fromY = moveList[currentMove][1] - ONE;
10717
10718         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10719
10720         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10721
10722         if (appData.highlightLastMove) {
10723             SetHighlights(fromX, fromY, toX, toY);
10724         }
10725     }
10726     DisplayMove(currentMove);
10727     SendMoveToProgram(currentMove++, &first);
10728     DisplayBothClocks();
10729     DrawPosition(FALSE, boards[currentMove]);
10730     // [HGM] PV info: always display, routine tests if empty
10731     DisplayComment(currentMove - 1, commentList[currentMove]);
10732     return TRUE;
10733 }
10734
10735
10736 int
10737 LoadGameOneMove(readAhead)
10738      ChessMove readAhead;
10739 {
10740     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10741     char promoChar = NULLCHAR;
10742     ChessMove moveType;
10743     char move[MSG_SIZ];
10744     char *p, *q;
10745
10746     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10747         gameMode != AnalyzeMode && gameMode != Training) {
10748         gameFileFP = NULL;
10749         return FALSE;
10750     }
10751
10752     yyboardindex = forwardMostMove;
10753     if (readAhead != EndOfFile) {
10754       moveType = readAhead;
10755     } else {
10756       if (gameFileFP == NULL)
10757           return FALSE;
10758       moveType = (ChessMove) Myylex();
10759     }
10760
10761     done = FALSE;
10762     switch (moveType) {
10763       case Comment:
10764         if (appData.debugMode)
10765           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10766         p = yy_text;
10767
10768         /* append the comment but don't display it */
10769         AppendComment(currentMove, p, FALSE);
10770         return TRUE;
10771
10772       case WhiteCapturesEnPassant:
10773       case BlackCapturesEnPassant:
10774       case WhitePromotion:
10775       case BlackPromotion:
10776       case WhiteNonPromotion:
10777       case BlackNonPromotion:
10778       case NormalMove:
10779       case WhiteKingSideCastle:
10780       case WhiteQueenSideCastle:
10781       case BlackKingSideCastle:
10782       case BlackQueenSideCastle:
10783       case WhiteKingSideCastleWild:
10784       case WhiteQueenSideCastleWild:
10785       case BlackKingSideCastleWild:
10786       case BlackQueenSideCastleWild:
10787       /* PUSH Fabien */
10788       case WhiteHSideCastleFR:
10789       case WhiteASideCastleFR:
10790       case BlackHSideCastleFR:
10791       case BlackASideCastleFR:
10792       /* POP Fabien */
10793         if (appData.debugMode)
10794           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10795         fromX = currentMoveString[0] - AAA;
10796         fromY = currentMoveString[1] - ONE;
10797         toX = currentMoveString[2] - AAA;
10798         toY = currentMoveString[3] - ONE;
10799         promoChar = currentMoveString[4];
10800         break;
10801
10802       case WhiteDrop:
10803       case BlackDrop:
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10806         fromX = moveType == WhiteDrop ?
10807           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10808         (int) CharToPiece(ToLower(currentMoveString[0]));
10809         fromY = DROP_RANK;
10810         toX = currentMoveString[2] - AAA;
10811         toY = currentMoveString[3] - ONE;
10812         break;
10813
10814       case WhiteWins:
10815       case BlackWins:
10816       case GameIsDrawn:
10817       case GameUnfinished:
10818         if (appData.debugMode)
10819           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10820         p = strchr(yy_text, '{');
10821         if (p == NULL) p = strchr(yy_text, '(');
10822         if (p == NULL) {
10823             p = yy_text;
10824             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10825         } else {
10826             q = strchr(p, *p == '{' ? '}' : ')');
10827             if (q != NULL) *q = NULLCHAR;
10828             p++;
10829         }
10830         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10831         GameEnds(moveType, p, GE_FILE);
10832         done = TRUE;
10833         if (cmailMsgLoaded) {
10834             ClearHighlights();
10835             flipView = WhiteOnMove(currentMove);
10836             if (moveType == GameUnfinished) flipView = !flipView;
10837             if (appData.debugMode)
10838               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10839         }
10840         break;
10841
10842       case EndOfFile:
10843         if (appData.debugMode)
10844           fprintf(debugFP, "Parser hit end of file\n");
10845         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10846           case MT_NONE:
10847           case MT_CHECK:
10848             break;
10849           case MT_CHECKMATE:
10850           case MT_STAINMATE:
10851             if (WhiteOnMove(currentMove)) {
10852                 GameEnds(BlackWins, "Black mates", GE_FILE);
10853             } else {
10854                 GameEnds(WhiteWins, "White mates", GE_FILE);
10855             }
10856             break;
10857           case MT_STALEMATE:
10858             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10859             break;
10860         }
10861         done = TRUE;
10862         break;
10863
10864       case MoveNumberOne:
10865         if (lastLoadGameStart == GNUChessGame) {
10866             /* GNUChessGames have numbers, but they aren't move numbers */
10867             if (appData.debugMode)
10868               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10869                       yy_text, (int) moveType);
10870             return LoadGameOneMove(EndOfFile); /* tail recursion */
10871         }
10872         /* else fall thru */
10873
10874       case XBoardGame:
10875       case GNUChessGame:
10876       case PGNTag:
10877         /* Reached start of next game in file */
10878         if (appData.debugMode)
10879           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10880         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10881           case MT_NONE:
10882           case MT_CHECK:
10883             break;
10884           case MT_CHECKMATE:
10885           case MT_STAINMATE:
10886             if (WhiteOnMove(currentMove)) {
10887                 GameEnds(BlackWins, "Black mates", GE_FILE);
10888             } else {
10889                 GameEnds(WhiteWins, "White mates", GE_FILE);
10890             }
10891             break;
10892           case MT_STALEMATE:
10893             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10894             break;
10895         }
10896         done = TRUE;
10897         break;
10898
10899       case PositionDiagram:     /* should not happen; ignore */
10900       case ElapsedTime:         /* ignore */
10901       case NAG:                 /* ignore */
10902         if (appData.debugMode)
10903           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10904                   yy_text, (int) moveType);
10905         return LoadGameOneMove(EndOfFile); /* tail recursion */
10906
10907       case IllegalMove:
10908         if (appData.testLegality) {
10909             if (appData.debugMode)
10910               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10911             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10912                     (forwardMostMove / 2) + 1,
10913                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10914             DisplayError(move, 0);
10915             done = TRUE;
10916         } else {
10917             if (appData.debugMode)
10918               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10919                       yy_text, currentMoveString);
10920             fromX = currentMoveString[0] - AAA;
10921             fromY = currentMoveString[1] - ONE;
10922             toX = currentMoveString[2] - AAA;
10923             toY = currentMoveString[3] - ONE;
10924             promoChar = currentMoveString[4];
10925         }
10926         break;
10927
10928       case AmbiguousMove:
10929         if (appData.debugMode)
10930           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10931         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10932                 (forwardMostMove / 2) + 1,
10933                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10934         DisplayError(move, 0);
10935         done = TRUE;
10936         break;
10937
10938       default:
10939       case ImpossibleMove:
10940         if (appData.debugMode)
10941           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10942         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10943                 (forwardMostMove / 2) + 1,
10944                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10945         DisplayError(move, 0);
10946         done = TRUE;
10947         break;
10948     }
10949
10950     if (done) {
10951         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10952             DrawPosition(FALSE, boards[currentMove]);
10953             DisplayBothClocks();
10954             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10955               DisplayComment(currentMove - 1, commentList[currentMove]);
10956         }
10957         (void) StopLoadGameTimer();
10958         gameFileFP = NULL;
10959         cmailOldMove = forwardMostMove;
10960         return FALSE;
10961     } else {
10962         /* currentMoveString is set as a side-effect of yylex */
10963
10964         thinkOutput[0] = NULLCHAR;
10965         MakeMove(fromX, fromY, toX, toY, promoChar);
10966         currentMove = forwardMostMove;
10967         return TRUE;
10968     }
10969 }
10970
10971 /* Load the nth game from the given file */
10972 int
10973 LoadGameFromFile(filename, n, title, useList)
10974      char *filename;
10975      int n;
10976      char *title;
10977      /*Boolean*/ int useList;
10978 {
10979     FILE *f;
10980     char buf[MSG_SIZ];
10981
10982     if (strcmp(filename, "-") == 0) {
10983         f = stdin;
10984         title = "stdin";
10985     } else {
10986         f = fopen(filename, "rb");
10987         if (f == NULL) {
10988           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10989             DisplayError(buf, errno);
10990             return FALSE;
10991         }
10992     }
10993     if (fseek(f, 0, 0) == -1) {
10994         /* f is not seekable; probably a pipe */
10995         useList = FALSE;
10996     }
10997     if (useList && n == 0) {
10998         int error = GameListBuild(f);
10999         if (error) {
11000             DisplayError(_("Cannot build game list"), error);
11001         } else if (!ListEmpty(&gameList) &&
11002                    ((ListGame *) gameList.tailPred)->number > 1) {
11003             GameListPopUp(f, title);
11004             return TRUE;
11005         }
11006         GameListDestroy();
11007         n = 1;
11008     }
11009     if (n == 0) n = 1;
11010     return LoadGame(f, n, title, FALSE);
11011 }
11012
11013
11014 void
11015 MakeRegisteredMove()
11016 {
11017     int fromX, fromY, toX, toY;
11018     char promoChar;
11019     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11020         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11021           case CMAIL_MOVE:
11022           case CMAIL_DRAW:
11023             if (appData.debugMode)
11024               fprintf(debugFP, "Restoring %s for game %d\n",
11025                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11026
11027             thinkOutput[0] = NULLCHAR;
11028             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11029             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11030             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11031             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11032             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11033             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11034             MakeMove(fromX, fromY, toX, toY, promoChar);
11035             ShowMove(fromX, fromY, toX, toY);
11036
11037             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11038               case MT_NONE:
11039               case MT_CHECK:
11040                 break;
11041
11042               case MT_CHECKMATE:
11043               case MT_STAINMATE:
11044                 if (WhiteOnMove(currentMove)) {
11045                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11046                 } else {
11047                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11048                 }
11049                 break;
11050
11051               case MT_STALEMATE:
11052                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11053                 break;
11054             }
11055
11056             break;
11057
11058           case CMAIL_RESIGN:
11059             if (WhiteOnMove(currentMove)) {
11060                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11061             } else {
11062                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11063             }
11064             break;
11065
11066           case CMAIL_ACCEPT:
11067             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11068             break;
11069
11070           default:
11071             break;
11072         }
11073     }
11074
11075     return;
11076 }
11077
11078 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11079 int
11080 CmailLoadGame(f, gameNumber, title, useList)
11081      FILE *f;
11082      int gameNumber;
11083      char *title;
11084      int useList;
11085 {
11086     int retVal;
11087
11088     if (gameNumber > nCmailGames) {
11089         DisplayError(_("No more games in this message"), 0);
11090         return FALSE;
11091     }
11092     if (f == lastLoadGameFP) {
11093         int offset = gameNumber - lastLoadGameNumber;
11094         if (offset == 0) {
11095             cmailMsg[0] = NULLCHAR;
11096             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11097                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11098                 nCmailMovesRegistered--;
11099             }
11100             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11101             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11102                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11103             }
11104         } else {
11105             if (! RegisterMove()) return FALSE;
11106         }
11107     }
11108
11109     retVal = LoadGame(f, gameNumber, title, useList);
11110
11111     /* Make move registered during previous look at this game, if any */
11112     MakeRegisteredMove();
11113
11114     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11115         commentList[currentMove]
11116           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11117         DisplayComment(currentMove - 1, commentList[currentMove]);
11118     }
11119
11120     return retVal;
11121 }
11122
11123 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11124 int
11125 ReloadGame(offset)
11126      int offset;
11127 {
11128     int gameNumber = lastLoadGameNumber + offset;
11129     if (lastLoadGameFP == NULL) {
11130         DisplayError(_("No game has been loaded yet"), 0);
11131         return FALSE;
11132     }
11133     if (gameNumber <= 0) {
11134         DisplayError(_("Can't back up any further"), 0);
11135         return FALSE;
11136     }
11137     if (cmailMsgLoaded) {
11138         return CmailLoadGame(lastLoadGameFP, gameNumber,
11139                              lastLoadGameTitle, lastLoadGameUseList);
11140     } else {
11141         return LoadGame(lastLoadGameFP, gameNumber,
11142                         lastLoadGameTitle, lastLoadGameUseList);
11143     }
11144 }
11145
11146 int keys[EmptySquare+1];
11147
11148 int
11149 PositionMatches(Board b1, Board b2)
11150 {
11151     int r, f, sum=0;
11152     switch(appData.searchMode) {
11153         case 1: return CompareWithRights(b1, b2);
11154         case 2:
11155             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11156                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11157             }
11158             return TRUE;
11159         case 3:
11160             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11161               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11162                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11163             }
11164             return sum==0;
11165         case 4:
11166             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11167                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11168             }
11169             return sum==0;
11170     }
11171     return TRUE;
11172 }
11173
11174 GameInfo dummyInfo;
11175
11176 int GameContainsPosition(FILE *f, ListGame *lg)
11177 {
11178     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11179     int fromX, fromY, toX, toY;
11180     char promoChar;
11181     static int initDone=FALSE;
11182
11183     if(!initDone) {
11184         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11185         initDone = TRUE;
11186     }
11187     dummyInfo.variant = VariantNormal;
11188     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11189     dummyInfo.whiteRating = 0;
11190     dummyInfo.blackRating = 0;
11191     FREE(dummyInfo.date); dummyInfo.date = NULL;
11192     fseek(f, lg->offset, 0);
11193     yynewfile(f);
11194     CopyBoard(boards[scratch], initialPosition); // default start position
11195     while(1) {
11196         yyboardindex = scratch + (plyNr&1);
11197       quickFlag = 1;
11198         next = Myylex();
11199       quickFlag = 0;
11200         switch(next) {
11201             case PGNTag:
11202                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11203 #if 0
11204                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11205                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11206 #else
11207                 // do it ourselves avoiding malloc
11208                 { char *p = yy_text+1, *q;
11209                   while(!isdigit(*p) && !isalpha(*p)) p++;
11210                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11211                   *p = NULLCHAR;
11212                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11213                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11214                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11215                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11216                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11217                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11218                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11219                 }
11220 #endif
11221             default:
11222                 continue;
11223
11224             case XBoardGame:
11225             case GNUChessGame:
11226                 if(plyNr) return -1; // after we have seen moves, this is for new game
11227               continue;
11228
11229             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11230             case ImpossibleMove:
11231             case WhiteWins: // game ends here with these four
11232             case BlackWins:
11233             case GameIsDrawn:
11234             case GameUnfinished:
11235                 return -1;
11236
11237             case IllegalMove:
11238                 if(appData.testLegality) return -1;
11239             case WhiteCapturesEnPassant:
11240             case BlackCapturesEnPassant:
11241             case WhitePromotion:
11242             case BlackPromotion:
11243             case WhiteNonPromotion:
11244             case BlackNonPromotion:
11245             case NormalMove:
11246             case WhiteKingSideCastle:
11247             case WhiteQueenSideCastle:
11248             case BlackKingSideCastle:
11249             case BlackQueenSideCastle:
11250             case WhiteKingSideCastleWild:
11251             case WhiteQueenSideCastleWild:
11252             case BlackKingSideCastleWild:
11253             case BlackQueenSideCastleWild:
11254             case WhiteHSideCastleFR:
11255             case WhiteASideCastleFR:
11256             case BlackHSideCastleFR:
11257             case BlackASideCastleFR:
11258                 fromX = currentMoveString[0] - AAA;
11259                 fromY = currentMoveString[1] - ONE;
11260                 toX = currentMoveString[2] - AAA;
11261                 toY = currentMoveString[3] - ONE;
11262                 promoChar = currentMoveString[4];
11263                 break;
11264             case WhiteDrop:
11265             case BlackDrop:
11266                 fromX = next == WhiteDrop ?
11267                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11268                   (int) CharToPiece(ToLower(currentMoveString[0]));
11269                 fromY = DROP_RANK;
11270                 toX = currentMoveString[2] - AAA;
11271                 toY = currentMoveString[3] - ONE;
11272                 break;
11273         }
11274         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11275         if(plyNr == 0) { // but first figure out variant and initial position
11276             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11277             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11278             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11279             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11280             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11281             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11282         }
11283         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11284         plyNr++;
11285         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11286         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11287     }
11288 }
11289
11290 /* Load the nth game from open file f */
11291 int
11292 LoadGame(f, gameNumber, title, useList)
11293      FILE *f;
11294      int gameNumber;
11295      char *title;
11296      int useList;
11297 {
11298     ChessMove cm;
11299     char buf[MSG_SIZ];
11300     int gn = gameNumber;
11301     ListGame *lg = NULL;
11302     int numPGNTags = 0;
11303     int err, pos = -1;
11304     GameMode oldGameMode;
11305     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11306
11307     if (appData.debugMode)
11308         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11309
11310     if (gameMode == Training )
11311         SetTrainingModeOff();
11312
11313     oldGameMode = gameMode;
11314     if (gameMode != BeginningOfGame) {
11315       Reset(FALSE, TRUE);
11316     }
11317
11318     gameFileFP = f;
11319     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11320         fclose(lastLoadGameFP);
11321     }
11322
11323     if (useList) {
11324         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11325
11326         if (lg) {
11327             fseek(f, lg->offset, 0);
11328             GameListHighlight(gameNumber);
11329             pos = lg->position;
11330             gn = 1;
11331         }
11332         else {
11333             DisplayError(_("Game number out of range"), 0);
11334             return FALSE;
11335         }
11336     } else {
11337         GameListDestroy();
11338         if (fseek(f, 0, 0) == -1) {
11339             if (f == lastLoadGameFP ?
11340                 gameNumber == lastLoadGameNumber + 1 :
11341                 gameNumber == 1) {
11342                 gn = 1;
11343             } else {
11344                 DisplayError(_("Can't seek on game file"), 0);
11345                 return FALSE;
11346             }
11347         }
11348     }
11349     lastLoadGameFP = f;
11350     lastLoadGameNumber = gameNumber;
11351     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11352     lastLoadGameUseList = useList;
11353
11354     yynewfile(f);
11355
11356     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11357       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11358                 lg->gameInfo.black);
11359             DisplayTitle(buf);
11360     } else if (*title != NULLCHAR) {
11361         if (gameNumber > 1) {
11362           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11363             DisplayTitle(buf);
11364         } else {
11365             DisplayTitle(title);
11366         }
11367     }
11368
11369     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11370         gameMode = PlayFromGameFile;
11371         ModeHighlight();
11372     }
11373
11374     currentMove = forwardMostMove = backwardMostMove = 0;
11375     CopyBoard(boards[0], initialPosition);
11376     StopClocks();
11377
11378     /*
11379      * Skip the first gn-1 games in the file.
11380      * Also skip over anything that precedes an identifiable
11381      * start of game marker, to avoid being confused by
11382      * garbage at the start of the file.  Currently
11383      * recognized start of game markers are the move number "1",
11384      * the pattern "gnuchess .* game", the pattern
11385      * "^[#;%] [^ ]* game file", and a PGN tag block.
11386      * A game that starts with one of the latter two patterns
11387      * will also have a move number 1, possibly
11388      * following a position diagram.
11389      * 5-4-02: Let's try being more lenient and allowing a game to
11390      * start with an unnumbered move.  Does that break anything?
11391      */
11392     cm = lastLoadGameStart = EndOfFile;
11393     while (gn > 0) {
11394         yyboardindex = forwardMostMove;
11395         cm = (ChessMove) Myylex();
11396         switch (cm) {
11397           case EndOfFile:
11398             if (cmailMsgLoaded) {
11399                 nCmailGames = CMAIL_MAX_GAMES - gn;
11400             } else {
11401                 Reset(TRUE, TRUE);
11402                 DisplayError(_("Game not found in file"), 0);
11403             }
11404             return FALSE;
11405
11406           case GNUChessGame:
11407           case XBoardGame:
11408             gn--;
11409             lastLoadGameStart = cm;
11410             break;
11411
11412           case MoveNumberOne:
11413             switch (lastLoadGameStart) {
11414               case GNUChessGame:
11415               case XBoardGame:
11416               case PGNTag:
11417                 break;
11418               case MoveNumberOne:
11419               case EndOfFile:
11420                 gn--;           /* count this game */
11421                 lastLoadGameStart = cm;
11422                 break;
11423               default:
11424                 /* impossible */
11425                 break;
11426             }
11427             break;
11428
11429           case PGNTag:
11430             switch (lastLoadGameStart) {
11431               case GNUChessGame:
11432               case PGNTag:
11433               case MoveNumberOne:
11434               case EndOfFile:
11435                 gn--;           /* count this game */
11436                 lastLoadGameStart = cm;
11437                 break;
11438               case XBoardGame:
11439                 lastLoadGameStart = cm; /* game counted already */
11440                 break;
11441               default:
11442                 /* impossible */
11443                 break;
11444             }
11445             if (gn > 0) {
11446                 do {
11447                     yyboardindex = forwardMostMove;
11448                     cm = (ChessMove) Myylex();
11449                 } while (cm == PGNTag || cm == Comment);
11450             }
11451             break;
11452
11453           case WhiteWins:
11454           case BlackWins:
11455           case GameIsDrawn:
11456             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11457                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11458                     != CMAIL_OLD_RESULT) {
11459                     nCmailResults ++ ;
11460                     cmailResult[  CMAIL_MAX_GAMES
11461                                 - gn - 1] = CMAIL_OLD_RESULT;
11462                 }
11463             }
11464             break;
11465
11466           case NormalMove:
11467             /* Only a NormalMove can be at the start of a game
11468              * without a position diagram. */
11469             if (lastLoadGameStart == EndOfFile ) {
11470               gn--;
11471               lastLoadGameStart = MoveNumberOne;
11472             }
11473             break;
11474
11475           default:
11476             break;
11477         }
11478     }
11479
11480     if (appData.debugMode)
11481       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11482
11483     if (cm == XBoardGame) {
11484         /* Skip any header junk before position diagram and/or move 1 */
11485         for (;;) {
11486             yyboardindex = forwardMostMove;
11487             cm = (ChessMove) Myylex();
11488
11489             if (cm == EndOfFile ||
11490                 cm == GNUChessGame || cm == XBoardGame) {
11491                 /* Empty game; pretend end-of-file and handle later */
11492                 cm = EndOfFile;
11493                 break;
11494             }
11495
11496             if (cm == MoveNumberOne || cm == PositionDiagram ||
11497                 cm == PGNTag || cm == Comment)
11498               break;
11499         }
11500     } else if (cm == GNUChessGame) {
11501         if (gameInfo.event != NULL) {
11502             free(gameInfo.event);
11503         }
11504         gameInfo.event = StrSave(yy_text);
11505     }
11506
11507     startedFromSetupPosition = FALSE;
11508     while (cm == PGNTag) {
11509         if (appData.debugMode)
11510           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11511         err = ParsePGNTag(yy_text, &gameInfo);
11512         if (!err) numPGNTags++;
11513
11514         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11515         if(gameInfo.variant != oldVariant) {
11516             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11517             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11518             InitPosition(TRUE);
11519             oldVariant = gameInfo.variant;
11520             if (appData.debugMode)
11521               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11522         }
11523
11524
11525         if (gameInfo.fen != NULL) {
11526           Board initial_position;
11527           startedFromSetupPosition = TRUE;
11528           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11529             Reset(TRUE, TRUE);
11530             DisplayError(_("Bad FEN position in file"), 0);
11531             return FALSE;
11532           }
11533           CopyBoard(boards[0], initial_position);
11534           if (blackPlaysFirst) {
11535             currentMove = forwardMostMove = backwardMostMove = 1;
11536             CopyBoard(boards[1], initial_position);
11537             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11538             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11539             timeRemaining[0][1] = whiteTimeRemaining;
11540             timeRemaining[1][1] = blackTimeRemaining;
11541             if (commentList[0] != NULL) {
11542               commentList[1] = commentList[0];
11543               commentList[0] = NULL;
11544             }
11545           } else {
11546             currentMove = forwardMostMove = backwardMostMove = 0;
11547           }
11548           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11549           {   int i;
11550               initialRulePlies = FENrulePlies;
11551               for( i=0; i< nrCastlingRights; i++ )
11552                   initialRights[i] = initial_position[CASTLING][i];
11553           }
11554           yyboardindex = forwardMostMove;
11555           free(gameInfo.fen);
11556           gameInfo.fen = NULL;
11557         }
11558
11559         yyboardindex = forwardMostMove;
11560         cm = (ChessMove) Myylex();
11561
11562         /* Handle comments interspersed among the tags */
11563         while (cm == Comment) {
11564             char *p;
11565             if (appData.debugMode)
11566               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11567             p = yy_text;
11568             AppendComment(currentMove, p, FALSE);
11569             yyboardindex = forwardMostMove;
11570             cm = (ChessMove) Myylex();
11571         }
11572     }
11573
11574     /* don't rely on existence of Event tag since if game was
11575      * pasted from clipboard the Event tag may not exist
11576      */
11577     if (numPGNTags > 0){
11578         char *tags;
11579         if (gameInfo.variant == VariantNormal) {
11580           VariantClass v = StringToVariant(gameInfo.event);
11581           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11582           if(v < VariantShogi) gameInfo.variant = v;
11583         }
11584         if (!matchMode) {
11585           if( appData.autoDisplayTags ) {
11586             tags = PGNTags(&gameInfo);
11587             TagsPopUp(tags, CmailMsg());
11588             free(tags);
11589           }
11590         }
11591     } else {
11592         /* Make something up, but don't display it now */
11593         SetGameInfo();
11594         TagsPopDown();
11595     }
11596
11597     if (cm == PositionDiagram) {
11598         int i, j;
11599         char *p;
11600         Board initial_position;
11601
11602         if (appData.debugMode)
11603           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11604
11605         if (!startedFromSetupPosition) {
11606             p = yy_text;
11607             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11608               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11609                 switch (*p) {
11610                   case '{':
11611                   case '[':
11612                   case '-':
11613                   case ' ':
11614                   case '\t':
11615                   case '\n':
11616                   case '\r':
11617                     break;
11618                   default:
11619                     initial_position[i][j++] = CharToPiece(*p);
11620                     break;
11621                 }
11622             while (*p == ' ' || *p == '\t' ||
11623                    *p == '\n' || *p == '\r') p++;
11624
11625             if (strncmp(p, "black", strlen("black"))==0)
11626               blackPlaysFirst = TRUE;
11627             else
11628               blackPlaysFirst = FALSE;
11629             startedFromSetupPosition = TRUE;
11630
11631             CopyBoard(boards[0], initial_position);
11632             if (blackPlaysFirst) {
11633                 currentMove = forwardMostMove = backwardMostMove = 1;
11634                 CopyBoard(boards[1], initial_position);
11635                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11636                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11637                 timeRemaining[0][1] = whiteTimeRemaining;
11638                 timeRemaining[1][1] = blackTimeRemaining;
11639                 if (commentList[0] != NULL) {
11640                     commentList[1] = commentList[0];
11641                     commentList[0] = NULL;
11642                 }
11643             } else {
11644                 currentMove = forwardMostMove = backwardMostMove = 0;
11645             }
11646         }
11647         yyboardindex = forwardMostMove;
11648         cm = (ChessMove) Myylex();
11649     }
11650
11651     if (first.pr == NoProc) {
11652         StartChessProgram(&first);
11653     }
11654     InitChessProgram(&first, FALSE);
11655     SendToProgram("force\n", &first);
11656     if (startedFromSetupPosition) {
11657         SendBoard(&first, forwardMostMove);
11658     if (appData.debugMode) {
11659         fprintf(debugFP, "Load Game\n");
11660     }
11661         DisplayBothClocks();
11662     }
11663
11664     /* [HGM] server: flag to write setup moves in broadcast file as one */
11665     loadFlag = appData.suppressLoadMoves;
11666
11667     while (cm == Comment) {
11668         char *p;
11669         if (appData.debugMode)
11670           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11671         p = yy_text;
11672         AppendComment(currentMove, p, FALSE);
11673         yyboardindex = forwardMostMove;
11674         cm = (ChessMove) Myylex();
11675     }
11676
11677     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11678         cm == WhiteWins || cm == BlackWins ||
11679         cm == GameIsDrawn || cm == GameUnfinished) {
11680         DisplayMessage("", _("No moves in game"));
11681         if (cmailMsgLoaded) {
11682             if (appData.debugMode)
11683               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11684             ClearHighlights();
11685             flipView = FALSE;
11686         }
11687         DrawPosition(FALSE, boards[currentMove]);
11688         DisplayBothClocks();
11689         gameMode = EditGame;
11690         ModeHighlight();
11691         gameFileFP = NULL;
11692         cmailOldMove = 0;
11693         return TRUE;
11694     }
11695
11696     // [HGM] PV info: routine tests if comment empty
11697     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11698         DisplayComment(currentMove - 1, commentList[currentMove]);
11699     }
11700     if (!matchMode && appData.timeDelay != 0)
11701       DrawPosition(FALSE, boards[currentMove]);
11702
11703     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11704       programStats.ok_to_send = 1;
11705     }
11706
11707     /* if the first token after the PGN tags is a move
11708      * and not move number 1, retrieve it from the parser
11709      */
11710     if (cm != MoveNumberOne)
11711         LoadGameOneMove(cm);
11712
11713     /* load the remaining moves from the file */
11714     while (LoadGameOneMove(EndOfFile)) {
11715       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11716       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11717     }
11718
11719     /* rewind to the start of the game */
11720     currentMove = backwardMostMove;
11721
11722     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11723
11724     if (oldGameMode == AnalyzeFile ||
11725         oldGameMode == AnalyzeMode) {
11726       AnalyzeFileEvent();
11727     }
11728
11729     if (!matchMode && pos >= 0) {
11730         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11731     } else
11732     if (matchMode || appData.timeDelay == 0) {
11733       ToEndEvent();
11734     } else if (appData.timeDelay > 0) {
11735       AutoPlayGameLoop();
11736     }
11737
11738     if (appData.debugMode)
11739         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11740
11741     loadFlag = 0; /* [HGM] true game starts */
11742     return TRUE;
11743 }
11744
11745 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11746 int
11747 ReloadPosition(offset)
11748      int offset;
11749 {
11750     int positionNumber = lastLoadPositionNumber + offset;
11751     if (lastLoadPositionFP == NULL) {
11752         DisplayError(_("No position has been loaded yet"), 0);
11753         return FALSE;
11754     }
11755     if (positionNumber <= 0) {
11756         DisplayError(_("Can't back up any further"), 0);
11757         return FALSE;
11758     }
11759     return LoadPosition(lastLoadPositionFP, positionNumber,
11760                         lastLoadPositionTitle);
11761 }
11762
11763 /* Load the nth position from the given file */
11764 int
11765 LoadPositionFromFile(filename, n, title)
11766      char *filename;
11767      int n;
11768      char *title;
11769 {
11770     FILE *f;
11771     char buf[MSG_SIZ];
11772
11773     if (strcmp(filename, "-") == 0) {
11774         return LoadPosition(stdin, n, "stdin");
11775     } else {
11776         f = fopen(filename, "rb");
11777         if (f == NULL) {
11778             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11779             DisplayError(buf, errno);
11780             return FALSE;
11781         } else {
11782             return LoadPosition(f, n, title);
11783         }
11784     }
11785 }
11786
11787 /* Load the nth position from the given open file, and close it */
11788 int
11789 LoadPosition(f, positionNumber, title)
11790      FILE *f;
11791      int positionNumber;
11792      char *title;
11793 {
11794     char *p, line[MSG_SIZ];
11795     Board initial_position;
11796     int i, j, fenMode, pn;
11797
11798     if (gameMode == Training )
11799         SetTrainingModeOff();
11800
11801     if (gameMode != BeginningOfGame) {
11802         Reset(FALSE, TRUE);
11803     }
11804     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11805         fclose(lastLoadPositionFP);
11806     }
11807     if (positionNumber == 0) positionNumber = 1;
11808     lastLoadPositionFP = f;
11809     lastLoadPositionNumber = positionNumber;
11810     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11811     if (first.pr == NoProc && !appData.noChessProgram) {
11812       StartChessProgram(&first);
11813       InitChessProgram(&first, FALSE);
11814     }
11815     pn = positionNumber;
11816     if (positionNumber < 0) {
11817         /* Negative position number means to seek to that byte offset */
11818         if (fseek(f, -positionNumber, 0) == -1) {
11819             DisplayError(_("Can't seek on position file"), 0);
11820             return FALSE;
11821         };
11822         pn = 1;
11823     } else {
11824         if (fseek(f, 0, 0) == -1) {
11825             if (f == lastLoadPositionFP ?
11826                 positionNumber == lastLoadPositionNumber + 1 :
11827                 positionNumber == 1) {
11828                 pn = 1;
11829             } else {
11830                 DisplayError(_("Can't seek on position file"), 0);
11831                 return FALSE;
11832             }
11833         }
11834     }
11835     /* See if this file is FEN or old-style xboard */
11836     if (fgets(line, MSG_SIZ, f) == NULL) {
11837         DisplayError(_("Position not found in file"), 0);
11838         return FALSE;
11839     }
11840     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11841     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11842
11843     if (pn >= 2) {
11844         if (fenMode || line[0] == '#') pn--;
11845         while (pn > 0) {
11846             /* skip positions before number pn */
11847             if (fgets(line, MSG_SIZ, f) == NULL) {
11848                 Reset(TRUE, TRUE);
11849                 DisplayError(_("Position not found in file"), 0);
11850                 return FALSE;
11851             }
11852             if (fenMode || line[0] == '#') pn--;
11853         }
11854     }
11855
11856     if (fenMode) {
11857         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11858             DisplayError(_("Bad FEN position in file"), 0);
11859             return FALSE;
11860         }
11861     } else {
11862         (void) fgets(line, MSG_SIZ, f);
11863         (void) fgets(line, MSG_SIZ, f);
11864
11865         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11866             (void) fgets(line, MSG_SIZ, f);
11867             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11868                 if (*p == ' ')
11869                   continue;
11870                 initial_position[i][j++] = CharToPiece(*p);
11871             }
11872         }
11873
11874         blackPlaysFirst = FALSE;
11875         if (!feof(f)) {
11876             (void) fgets(line, MSG_SIZ, f);
11877             if (strncmp(line, "black", strlen("black"))==0)
11878               blackPlaysFirst = TRUE;
11879         }
11880     }
11881     startedFromSetupPosition = TRUE;
11882
11883     CopyBoard(boards[0], initial_position);
11884     if (blackPlaysFirst) {
11885         currentMove = forwardMostMove = backwardMostMove = 1;
11886         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11887         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11888         CopyBoard(boards[1], initial_position);
11889         DisplayMessage("", _("Black to play"));
11890     } else {
11891         currentMove = forwardMostMove = backwardMostMove = 0;
11892         DisplayMessage("", _("White to play"));
11893     }
11894     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11895     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
11896         SendToProgram("force\n", &first);
11897         SendBoard(&first, forwardMostMove);
11898     }
11899     if (appData.debugMode) {
11900 int i, j;
11901   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11902   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11903         fprintf(debugFP, "Load Position\n");
11904     }
11905
11906     if (positionNumber > 1) {
11907       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11908         DisplayTitle(line);
11909     } else {
11910         DisplayTitle(title);
11911     }
11912     gameMode = EditGame;
11913     ModeHighlight();
11914     ResetClocks();
11915     timeRemaining[0][1] = whiteTimeRemaining;
11916     timeRemaining[1][1] = blackTimeRemaining;
11917     DrawPosition(FALSE, boards[currentMove]);
11918
11919     return TRUE;
11920 }
11921
11922
11923 void
11924 CopyPlayerNameIntoFileName(dest, src)
11925      char **dest, *src;
11926 {
11927     while (*src != NULLCHAR && *src != ',') {
11928         if (*src == ' ') {
11929             *(*dest)++ = '_';
11930             src++;
11931         } else {
11932             *(*dest)++ = *src++;
11933         }
11934     }
11935 }
11936
11937 char *DefaultFileName(ext)
11938      char *ext;
11939 {
11940     static char def[MSG_SIZ];
11941     char *p;
11942
11943     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11944         p = def;
11945         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11946         *p++ = '-';
11947         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11948         *p++ = '.';
11949         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11950     } else {
11951         def[0] = NULLCHAR;
11952     }
11953     return def;
11954 }
11955
11956 /* Save the current game to the given file */
11957 int
11958 SaveGameToFile(filename, append)
11959      char *filename;
11960      int append;
11961 {
11962     FILE *f;
11963     char buf[MSG_SIZ];
11964     int result, i, t,tot=0;
11965
11966     if (strcmp(filename, "-") == 0) {
11967         return SaveGame(stdout, 0, NULL);
11968     } else {
11969         for(i=0; i<10; i++) { // upto 10 tries
11970              f = fopen(filename, append ? "a" : "w");
11971              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
11972              if(f || errno != 13) break;
11973              DoSleep(t = 5 + random()%11); // wait 5-15 msec
11974              tot += t;
11975         }
11976         if (f == NULL) {
11977             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11978             DisplayError(buf, errno);
11979             return FALSE;
11980         } else {
11981             safeStrCpy(buf, lastMsg, MSG_SIZ);
11982             DisplayMessage(_("Waiting for access to save file"), "");
11983             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11984             DisplayMessage(_("Saving game"), "");
11985             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11986             result = SaveGame(f, 0, NULL);
11987             DisplayMessage(buf, "");
11988             return result;
11989         }
11990     }
11991 }
11992
11993 char *
11994 SavePart(str)
11995      char *str;
11996 {
11997     static char buf[MSG_SIZ];
11998     char *p;
11999
12000     p = strchr(str, ' ');
12001     if (p == NULL) return str;
12002     strncpy(buf, str, p - str);
12003     buf[p - str] = NULLCHAR;
12004     return buf;
12005 }
12006
12007 #define PGN_MAX_LINE 75
12008
12009 #define PGN_SIDE_WHITE  0
12010 #define PGN_SIDE_BLACK  1
12011
12012 /* [AS] */
12013 static int FindFirstMoveOutOfBook( int side )
12014 {
12015     int result = -1;
12016
12017     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12018         int index = backwardMostMove;
12019         int has_book_hit = 0;
12020
12021         if( (index % 2) != side ) {
12022             index++;
12023         }
12024
12025         while( index < forwardMostMove ) {
12026             /* Check to see if engine is in book */
12027             int depth = pvInfoList[index].depth;
12028             int score = pvInfoList[index].score;
12029             int in_book = 0;
12030
12031             if( depth <= 2 ) {
12032                 in_book = 1;
12033             }
12034             else if( score == 0 && depth == 63 ) {
12035                 in_book = 1; /* Zappa */
12036             }
12037             else if( score == 2 && depth == 99 ) {
12038                 in_book = 1; /* Abrok */
12039             }
12040
12041             has_book_hit += in_book;
12042
12043             if( ! in_book ) {
12044                 result = index;
12045
12046                 break;
12047             }
12048
12049             index += 2;
12050         }
12051     }
12052
12053     return result;
12054 }
12055
12056 /* [AS] */
12057 void GetOutOfBookInfo( char * buf )
12058 {
12059     int oob[2];
12060     int i;
12061     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12062
12063     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12064     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12065
12066     *buf = '\0';
12067
12068     if( oob[0] >= 0 || oob[1] >= 0 ) {
12069         for( i=0; i<2; i++ ) {
12070             int idx = oob[i];
12071
12072             if( idx >= 0 ) {
12073                 if( i > 0 && oob[0] >= 0 ) {
12074                     strcat( buf, "   " );
12075                 }
12076
12077                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12078                 sprintf( buf+strlen(buf), "%s%.2f",
12079                     pvInfoList[idx].score >= 0 ? "+" : "",
12080                     pvInfoList[idx].score / 100.0 );
12081             }
12082         }
12083     }
12084 }
12085
12086 /* Save game in PGN style and close the file */
12087 int
12088 SaveGamePGN(f)
12089      FILE *f;
12090 {
12091     int i, offset, linelen, newblock;
12092     time_t tm;
12093 //    char *movetext;
12094     char numtext[32];
12095     int movelen, numlen, blank;
12096     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12097
12098     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12099
12100     tm = time((time_t *) NULL);
12101
12102     PrintPGNTags(f, &gameInfo);
12103
12104     if (backwardMostMove > 0 || startedFromSetupPosition) {
12105         char *fen = PositionToFEN(backwardMostMove, NULL);
12106         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12107         fprintf(f, "\n{--------------\n");
12108         PrintPosition(f, backwardMostMove);
12109         fprintf(f, "--------------}\n");
12110         free(fen);
12111     }
12112     else {
12113         /* [AS] Out of book annotation */
12114         if( appData.saveOutOfBookInfo ) {
12115             char buf[64];
12116
12117             GetOutOfBookInfo( buf );
12118
12119             if( buf[0] != '\0' ) {
12120                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12121             }
12122         }
12123
12124         fprintf(f, "\n");
12125     }
12126
12127     i = backwardMostMove;
12128     linelen = 0;
12129     newblock = TRUE;
12130
12131     while (i < forwardMostMove) {
12132         /* Print comments preceding this move */
12133         if (commentList[i] != NULL) {
12134             if (linelen > 0) fprintf(f, "\n");
12135             fprintf(f, "%s", commentList[i]);
12136             linelen = 0;
12137             newblock = TRUE;
12138         }
12139
12140         /* Format move number */
12141         if ((i % 2) == 0)
12142           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12143         else
12144           if (newblock)
12145             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12146           else
12147             numtext[0] = NULLCHAR;
12148
12149         numlen = strlen(numtext);
12150         newblock = FALSE;
12151
12152         /* Print move number */
12153         blank = linelen > 0 && numlen > 0;
12154         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12155             fprintf(f, "\n");
12156             linelen = 0;
12157             blank = 0;
12158         }
12159         if (blank) {
12160             fprintf(f, " ");
12161             linelen++;
12162         }
12163         fprintf(f, "%s", numtext);
12164         linelen += numlen;
12165
12166         /* Get move */
12167         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12168         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12169
12170         /* Print move */
12171         blank = linelen > 0 && movelen > 0;
12172         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12173             fprintf(f, "\n");
12174             linelen = 0;
12175             blank = 0;
12176         }
12177         if (blank) {
12178             fprintf(f, " ");
12179             linelen++;
12180         }
12181         fprintf(f, "%s", move_buffer);
12182         linelen += movelen;
12183
12184         /* [AS] Add PV info if present */
12185         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12186             /* [HGM] add time */
12187             char buf[MSG_SIZ]; int seconds;
12188
12189             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12190
12191             if( seconds <= 0)
12192               buf[0] = 0;
12193             else
12194               if( seconds < 30 )
12195                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12196               else
12197                 {
12198                   seconds = (seconds + 4)/10; // round to full seconds
12199                   if( seconds < 60 )
12200                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12201                   else
12202                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12203                 }
12204
12205             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12206                       pvInfoList[i].score >= 0 ? "+" : "",
12207                       pvInfoList[i].score / 100.0,
12208                       pvInfoList[i].depth,
12209                       buf );
12210
12211             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12212
12213             /* Print score/depth */
12214             blank = linelen > 0 && movelen > 0;
12215             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12216                 fprintf(f, "\n");
12217                 linelen = 0;
12218                 blank = 0;
12219             }
12220             if (blank) {
12221                 fprintf(f, " ");
12222                 linelen++;
12223             }
12224             fprintf(f, "%s", move_buffer);
12225             linelen += movelen;
12226         }
12227
12228         i++;
12229     }
12230
12231     /* Start a new line */
12232     if (linelen > 0) fprintf(f, "\n");
12233
12234     /* Print comments after last move */
12235     if (commentList[i] != NULL) {
12236         fprintf(f, "%s\n", commentList[i]);
12237     }
12238
12239     /* Print result */
12240     if (gameInfo.resultDetails != NULL &&
12241         gameInfo.resultDetails[0] != NULLCHAR) {
12242         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12243                 PGNResult(gameInfo.result));
12244     } else {
12245         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12246     }
12247
12248     fclose(f);
12249     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12250     return TRUE;
12251 }
12252
12253 /* Save game in old style and close the file */
12254 int
12255 SaveGameOldStyle(f)
12256      FILE *f;
12257 {
12258     int i, offset;
12259     time_t tm;
12260
12261     tm = time((time_t *) NULL);
12262
12263     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12264     PrintOpponents(f);
12265
12266     if (backwardMostMove > 0 || startedFromSetupPosition) {
12267         fprintf(f, "\n[--------------\n");
12268         PrintPosition(f, backwardMostMove);
12269         fprintf(f, "--------------]\n");
12270     } else {
12271         fprintf(f, "\n");
12272     }
12273
12274     i = backwardMostMove;
12275     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12276
12277     while (i < forwardMostMove) {
12278         if (commentList[i] != NULL) {
12279             fprintf(f, "[%s]\n", commentList[i]);
12280         }
12281
12282         if ((i % 2) == 1) {
12283             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12284             i++;
12285         } else {
12286             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12287             i++;
12288             if (commentList[i] != NULL) {
12289                 fprintf(f, "\n");
12290                 continue;
12291             }
12292             if (i >= forwardMostMove) {
12293                 fprintf(f, "\n");
12294                 break;
12295             }
12296             fprintf(f, "%s\n", parseList[i]);
12297             i++;
12298         }
12299     }
12300
12301     if (commentList[i] != NULL) {
12302         fprintf(f, "[%s]\n", commentList[i]);
12303     }
12304
12305     /* This isn't really the old style, but it's close enough */
12306     if (gameInfo.resultDetails != NULL &&
12307         gameInfo.resultDetails[0] != NULLCHAR) {
12308         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12309                 gameInfo.resultDetails);
12310     } else {
12311         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12312     }
12313
12314     fclose(f);
12315     return TRUE;
12316 }
12317
12318 /* Save the current game to open file f and close the file */
12319 int
12320 SaveGame(f, dummy, dummy2)
12321      FILE *f;
12322      int dummy;
12323      char *dummy2;
12324 {
12325     if (gameMode == EditPosition) EditPositionDone(TRUE);
12326     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12327     if (appData.oldSaveStyle)
12328       return SaveGameOldStyle(f);
12329     else
12330       return SaveGamePGN(f);
12331 }
12332
12333 /* Save the current position to the given file */
12334 int
12335 SavePositionToFile(filename)
12336      char *filename;
12337 {
12338     FILE *f;
12339     char buf[MSG_SIZ];
12340
12341     if (strcmp(filename, "-") == 0) {
12342         return SavePosition(stdout, 0, NULL);
12343     } else {
12344         f = fopen(filename, "a");
12345         if (f == NULL) {
12346             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12347             DisplayError(buf, errno);
12348             return FALSE;
12349         } else {
12350             safeStrCpy(buf, lastMsg, MSG_SIZ);
12351             DisplayMessage(_("Waiting for access to save file"), "");
12352             flock(fileno(f), LOCK_EX); // [HGM] lock
12353             DisplayMessage(_("Saving position"), "");
12354             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12355             SavePosition(f, 0, NULL);
12356             DisplayMessage(buf, "");
12357             return TRUE;
12358         }
12359     }
12360 }
12361
12362 /* Save the current position to the given open file and close the file */
12363 int
12364 SavePosition(f, dummy, dummy2)
12365      FILE *f;
12366      int dummy;
12367      char *dummy2;
12368 {
12369     time_t tm;
12370     char *fen;
12371
12372     if (gameMode == EditPosition) EditPositionDone(TRUE);
12373     if (appData.oldSaveStyle) {
12374         tm = time((time_t *) NULL);
12375
12376         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12377         PrintOpponents(f);
12378         fprintf(f, "[--------------\n");
12379         PrintPosition(f, currentMove);
12380         fprintf(f, "--------------]\n");
12381     } else {
12382         fen = PositionToFEN(currentMove, NULL);
12383         fprintf(f, "%s\n", fen);
12384         free(fen);
12385     }
12386     fclose(f);
12387     return TRUE;
12388 }
12389
12390 void
12391 ReloadCmailMsgEvent(unregister)
12392      int unregister;
12393 {
12394 #if !WIN32
12395     static char *inFilename = NULL;
12396     static char *outFilename;
12397     int i;
12398     struct stat inbuf, outbuf;
12399     int status;
12400
12401     /* Any registered moves are unregistered if unregister is set, */
12402     /* i.e. invoked by the signal handler */
12403     if (unregister) {
12404         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12405             cmailMoveRegistered[i] = FALSE;
12406             if (cmailCommentList[i] != NULL) {
12407                 free(cmailCommentList[i]);
12408                 cmailCommentList[i] = NULL;
12409             }
12410         }
12411         nCmailMovesRegistered = 0;
12412     }
12413
12414     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12415         cmailResult[i] = CMAIL_NOT_RESULT;
12416     }
12417     nCmailResults = 0;
12418
12419     if (inFilename == NULL) {
12420         /* Because the filenames are static they only get malloced once  */
12421         /* and they never get freed                                      */
12422         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12423         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12424
12425         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12426         sprintf(outFilename, "%s.out", appData.cmailGameName);
12427     }
12428
12429     status = stat(outFilename, &outbuf);
12430     if (status < 0) {
12431         cmailMailedMove = FALSE;
12432     } else {
12433         status = stat(inFilename, &inbuf);
12434         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12435     }
12436
12437     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12438        counts the games, notes how each one terminated, etc.
12439
12440        It would be nice to remove this kludge and instead gather all
12441        the information while building the game list.  (And to keep it
12442        in the game list nodes instead of having a bunch of fixed-size
12443        parallel arrays.)  Note this will require getting each game's
12444        termination from the PGN tags, as the game list builder does
12445        not process the game moves.  --mann
12446        */
12447     cmailMsgLoaded = TRUE;
12448     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12449
12450     /* Load first game in the file or popup game menu */
12451     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12452
12453 #endif /* !WIN32 */
12454     return;
12455 }
12456
12457 int
12458 RegisterMove()
12459 {
12460     FILE *f;
12461     char string[MSG_SIZ];
12462
12463     if (   cmailMailedMove
12464         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12465         return TRUE;            /* Allow free viewing  */
12466     }
12467
12468     /* Unregister move to ensure that we don't leave RegisterMove        */
12469     /* with the move registered when the conditions for registering no   */
12470     /* longer hold                                                       */
12471     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12472         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12473         nCmailMovesRegistered --;
12474
12475         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12476           {
12477               free(cmailCommentList[lastLoadGameNumber - 1]);
12478               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12479           }
12480     }
12481
12482     if (cmailOldMove == -1) {
12483         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12484         return FALSE;
12485     }
12486
12487     if (currentMove > cmailOldMove + 1) {
12488         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12489         return FALSE;
12490     }
12491
12492     if (currentMove < cmailOldMove) {
12493         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12494         return FALSE;
12495     }
12496
12497     if (forwardMostMove > currentMove) {
12498         /* Silently truncate extra moves */
12499         TruncateGame();
12500     }
12501
12502     if (   (currentMove == cmailOldMove + 1)
12503         || (   (currentMove == cmailOldMove)
12504             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12505                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12506         if (gameInfo.result != GameUnfinished) {
12507             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12508         }
12509
12510         if (commentList[currentMove] != NULL) {
12511             cmailCommentList[lastLoadGameNumber - 1]
12512               = StrSave(commentList[currentMove]);
12513         }
12514         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12515
12516         if (appData.debugMode)
12517           fprintf(debugFP, "Saving %s for game %d\n",
12518                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12519
12520         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12521
12522         f = fopen(string, "w");
12523         if (appData.oldSaveStyle) {
12524             SaveGameOldStyle(f); /* also closes the file */
12525
12526             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12527             f = fopen(string, "w");
12528             SavePosition(f, 0, NULL); /* also closes the file */
12529         } else {
12530             fprintf(f, "{--------------\n");
12531             PrintPosition(f, currentMove);
12532             fprintf(f, "--------------}\n\n");
12533
12534             SaveGame(f, 0, NULL); /* also closes the file*/
12535         }
12536
12537         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12538         nCmailMovesRegistered ++;
12539     } else if (nCmailGames == 1) {
12540         DisplayError(_("You have not made a move yet"), 0);
12541         return FALSE;
12542     }
12543
12544     return TRUE;
12545 }
12546
12547 void
12548 MailMoveEvent()
12549 {
12550 #if !WIN32
12551     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12552     FILE *commandOutput;
12553     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12554     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12555     int nBuffers;
12556     int i;
12557     int archived;
12558     char *arcDir;
12559
12560     if (! cmailMsgLoaded) {
12561         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12562         return;
12563     }
12564
12565     if (nCmailGames == nCmailResults) {
12566         DisplayError(_("No unfinished games"), 0);
12567         return;
12568     }
12569
12570 #if CMAIL_PROHIBIT_REMAIL
12571     if (cmailMailedMove) {
12572       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);
12573         DisplayError(msg, 0);
12574         return;
12575     }
12576 #endif
12577
12578     if (! (cmailMailedMove || RegisterMove())) return;
12579
12580     if (   cmailMailedMove
12581         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12582       snprintf(string, MSG_SIZ, partCommandString,
12583                appData.debugMode ? " -v" : "", appData.cmailGameName);
12584         commandOutput = popen(string, "r");
12585
12586         if (commandOutput == NULL) {
12587             DisplayError(_("Failed to invoke cmail"), 0);
12588         } else {
12589             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12590                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12591             }
12592             if (nBuffers > 1) {
12593                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12594                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12595                 nBytes = MSG_SIZ - 1;
12596             } else {
12597                 (void) memcpy(msg, buffer, nBytes);
12598             }
12599             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12600
12601             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12602                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12603
12604                 archived = TRUE;
12605                 for (i = 0; i < nCmailGames; i ++) {
12606                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12607                         archived = FALSE;
12608                     }
12609                 }
12610                 if (   archived
12611                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12612                         != NULL)) {
12613                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12614                            arcDir,
12615                            appData.cmailGameName,
12616                            gameInfo.date);
12617                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12618                     cmailMsgLoaded = FALSE;
12619                 }
12620             }
12621
12622             DisplayInformation(msg);
12623             pclose(commandOutput);
12624         }
12625     } else {
12626         if ((*cmailMsg) != '\0') {
12627             DisplayInformation(cmailMsg);
12628         }
12629     }
12630
12631     return;
12632 #endif /* !WIN32 */
12633 }
12634
12635 char *
12636 CmailMsg()
12637 {
12638 #if WIN32
12639     return NULL;
12640 #else
12641     int  prependComma = 0;
12642     char number[5];
12643     char string[MSG_SIZ];       /* Space for game-list */
12644     int  i;
12645
12646     if (!cmailMsgLoaded) return "";
12647
12648     if (cmailMailedMove) {
12649       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12650     } else {
12651         /* Create a list of games left */
12652       snprintf(string, MSG_SIZ, "[");
12653         for (i = 0; i < nCmailGames; i ++) {
12654             if (! (   cmailMoveRegistered[i]
12655                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12656                 if (prependComma) {
12657                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12658                 } else {
12659                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12660                     prependComma = 1;
12661                 }
12662
12663                 strcat(string, number);
12664             }
12665         }
12666         strcat(string, "]");
12667
12668         if (nCmailMovesRegistered + nCmailResults == 0) {
12669             switch (nCmailGames) {
12670               case 1:
12671                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12672                 break;
12673
12674               case 2:
12675                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12676                 break;
12677
12678               default:
12679                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12680                          nCmailGames);
12681                 break;
12682             }
12683         } else {
12684             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12685               case 1:
12686                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12687                          string);
12688                 break;
12689
12690               case 0:
12691                 if (nCmailResults == nCmailGames) {
12692                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12693                 } else {
12694                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12695                 }
12696                 break;
12697
12698               default:
12699                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12700                          string);
12701             }
12702         }
12703     }
12704     return cmailMsg;
12705 #endif /* WIN32 */
12706 }
12707
12708 void
12709 ResetGameEvent()
12710 {
12711     if (gameMode == Training)
12712       SetTrainingModeOff();
12713
12714     Reset(TRUE, TRUE);
12715     cmailMsgLoaded = FALSE;
12716     if (appData.icsActive) {
12717       SendToICS(ics_prefix);
12718       SendToICS("refresh\n");
12719     }
12720 }
12721
12722 void
12723 ExitEvent(status)
12724      int status;
12725 {
12726     exiting++;
12727     if (exiting > 2) {
12728       /* Give up on clean exit */
12729       exit(status);
12730     }
12731     if (exiting > 1) {
12732       /* Keep trying for clean exit */
12733       return;
12734     }
12735
12736     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12737
12738     if (telnetISR != NULL) {
12739       RemoveInputSource(telnetISR);
12740     }
12741     if (icsPR != NoProc) {
12742       DestroyChildProcess(icsPR, TRUE);
12743     }
12744
12745     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12746     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12747
12748     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12749     /* make sure this other one finishes before killing it!                  */
12750     if(endingGame) { int count = 0;
12751         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12752         while(endingGame && count++ < 10) DoSleep(1);
12753         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12754     }
12755
12756     /* Kill off chess programs */
12757     if (first.pr != NoProc) {
12758         ExitAnalyzeMode();
12759
12760         DoSleep( appData.delayBeforeQuit );
12761         SendToProgram("quit\n", &first);
12762         DoSleep( appData.delayAfterQuit );
12763         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12764     }
12765     if (second.pr != NoProc) {
12766         DoSleep( appData.delayBeforeQuit );
12767         SendToProgram("quit\n", &second);
12768         DoSleep( appData.delayAfterQuit );
12769         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12770     }
12771     if (first.isr != NULL) {
12772         RemoveInputSource(first.isr);
12773     }
12774     if (second.isr != NULL) {
12775         RemoveInputSource(second.isr);
12776     }
12777
12778     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12779     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12780
12781     ShutDownFrontEnd();
12782     exit(status);
12783 }
12784
12785 void
12786 PauseEvent()
12787 {
12788     if (appData.debugMode)
12789         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12790     if (pausing) {
12791         pausing = FALSE;
12792         ModeHighlight();
12793         if (gameMode == MachinePlaysWhite ||
12794             gameMode == MachinePlaysBlack) {
12795             StartClocks();
12796         } else {
12797             DisplayBothClocks();
12798         }
12799         if (gameMode == PlayFromGameFile) {
12800             if (appData.timeDelay >= 0)
12801                 AutoPlayGameLoop();
12802         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12803             Reset(FALSE, TRUE);
12804             SendToICS(ics_prefix);
12805             SendToICS("refresh\n");
12806         } else if (currentMove < forwardMostMove) {
12807             ForwardInner(forwardMostMove);
12808         }
12809         pauseExamInvalid = FALSE;
12810     } else {
12811         switch (gameMode) {
12812           default:
12813             return;
12814           case IcsExamining:
12815             pauseExamForwardMostMove = forwardMostMove;
12816             pauseExamInvalid = FALSE;
12817             /* fall through */
12818           case IcsObserving:
12819           case IcsPlayingWhite:
12820           case IcsPlayingBlack:
12821             pausing = TRUE;
12822             ModeHighlight();
12823             return;
12824           case PlayFromGameFile:
12825             (void) StopLoadGameTimer();
12826             pausing = TRUE;
12827             ModeHighlight();
12828             break;
12829           case BeginningOfGame:
12830             if (appData.icsActive) return;
12831             /* else fall through */
12832           case MachinePlaysWhite:
12833           case MachinePlaysBlack:
12834           case TwoMachinesPlay:
12835             if (forwardMostMove == 0)
12836               return;           /* don't pause if no one has moved */
12837             if ((gameMode == MachinePlaysWhite &&
12838                  !WhiteOnMove(forwardMostMove)) ||
12839                 (gameMode == MachinePlaysBlack &&
12840                  WhiteOnMove(forwardMostMove))) {
12841                 StopClocks();
12842             }
12843             pausing = TRUE;
12844             ModeHighlight();
12845             break;
12846         }
12847     }
12848 }
12849
12850 void
12851 EditCommentEvent()
12852 {
12853     char title[MSG_SIZ];
12854
12855     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12856       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12857     } else {
12858       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12859                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12860                parseList[currentMove - 1]);
12861     }
12862
12863     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12864 }
12865
12866
12867 void
12868 EditTagsEvent()
12869 {
12870     char *tags = PGNTags(&gameInfo);
12871     bookUp = FALSE;
12872     EditTagsPopUp(tags, NULL);
12873     free(tags);
12874 }
12875
12876 void
12877 AnalyzeModeEvent()
12878 {
12879     if (appData.noChessProgram || gameMode == AnalyzeMode)
12880       return;
12881
12882     if (gameMode != AnalyzeFile) {
12883         if (!appData.icsEngineAnalyze) {
12884                EditGameEvent();
12885                if (gameMode != EditGame) return;
12886         }
12887         ResurrectChessProgram();
12888         SendToProgram("analyze\n", &first);
12889         first.analyzing = TRUE;
12890         /*first.maybeThinking = TRUE;*/
12891         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12892         EngineOutputPopUp();
12893     }
12894     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12895     pausing = FALSE;
12896     ModeHighlight();
12897     SetGameInfo();
12898
12899     StartAnalysisClock();
12900     GetTimeMark(&lastNodeCountTime);
12901     lastNodeCount = 0;
12902 }
12903
12904 void
12905 AnalyzeFileEvent()
12906 {
12907     if (appData.noChessProgram || gameMode == AnalyzeFile)
12908       return;
12909
12910     if (gameMode != AnalyzeMode) {
12911         EditGameEvent();
12912         if (gameMode != EditGame) return;
12913         ResurrectChessProgram();
12914         SendToProgram("analyze\n", &first);
12915         first.analyzing = TRUE;
12916         /*first.maybeThinking = TRUE;*/
12917         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12918         EngineOutputPopUp();
12919     }
12920     gameMode = AnalyzeFile;
12921     pausing = FALSE;
12922     ModeHighlight();
12923     SetGameInfo();
12924
12925     StartAnalysisClock();
12926     GetTimeMark(&lastNodeCountTime);
12927     lastNodeCount = 0;
12928     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12929 }
12930
12931 void
12932 MachineWhiteEvent()
12933 {
12934     char buf[MSG_SIZ];
12935     char *bookHit = NULL;
12936
12937     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12938       return;
12939
12940
12941     if (gameMode == PlayFromGameFile ||
12942         gameMode == TwoMachinesPlay  ||
12943         gameMode == Training         ||
12944         gameMode == AnalyzeMode      ||
12945         gameMode == EndOfGame)
12946         EditGameEvent();
12947
12948     if (gameMode == EditPosition)
12949         EditPositionDone(TRUE);
12950
12951     if (!WhiteOnMove(currentMove)) {
12952         DisplayError(_("It is not White's turn"), 0);
12953         return;
12954     }
12955
12956     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12957       ExitAnalyzeMode();
12958
12959     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12960         gameMode == AnalyzeFile)
12961         TruncateGame();
12962
12963     ResurrectChessProgram();    /* in case it isn't running */
12964     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12965         gameMode = MachinePlaysWhite;
12966         ResetClocks();
12967     } else
12968     gameMode = MachinePlaysWhite;
12969     pausing = FALSE;
12970     ModeHighlight();
12971     SetGameInfo();
12972     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12973     DisplayTitle(buf);
12974     if (first.sendName) {
12975       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12976       SendToProgram(buf, &first);
12977     }
12978     if (first.sendTime) {
12979       if (first.useColors) {
12980         SendToProgram("black\n", &first); /*gnu kludge*/
12981       }
12982       SendTimeRemaining(&first, TRUE);
12983     }
12984     if (first.useColors) {
12985       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12986     }
12987     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12988     SetMachineThinkingEnables();
12989     first.maybeThinking = TRUE;
12990     StartClocks();
12991     firstMove = FALSE;
12992
12993     if (appData.autoFlipView && !flipView) {
12994       flipView = !flipView;
12995       DrawPosition(FALSE, NULL);
12996       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12997     }
12998
12999     if(bookHit) { // [HGM] book: simulate book reply
13000         static char bookMove[MSG_SIZ]; // a bit generous?
13001
13002         programStats.nodes = programStats.depth = programStats.time =
13003         programStats.score = programStats.got_only_move = 0;
13004         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13005
13006         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13007         strcat(bookMove, bookHit);
13008         HandleMachineMove(bookMove, &first);
13009     }
13010 }
13011
13012 void
13013 MachineBlackEvent()
13014 {
13015   char buf[MSG_SIZ];
13016   char *bookHit = NULL;
13017
13018     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13019         return;
13020
13021
13022     if (gameMode == PlayFromGameFile ||
13023         gameMode == TwoMachinesPlay  ||
13024         gameMode == Training         ||
13025         gameMode == AnalyzeMode      ||
13026         gameMode == EndOfGame)
13027         EditGameEvent();
13028
13029     if (gameMode == EditPosition)
13030         EditPositionDone(TRUE);
13031
13032     if (WhiteOnMove(currentMove)) {
13033         DisplayError(_("It is not Black's turn"), 0);
13034         return;
13035     }
13036
13037     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13038       ExitAnalyzeMode();
13039
13040     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13041         gameMode == AnalyzeFile)
13042         TruncateGame();
13043
13044     ResurrectChessProgram();    /* in case it isn't running */
13045     gameMode = MachinePlaysBlack;
13046     pausing = FALSE;
13047     ModeHighlight();
13048     SetGameInfo();
13049     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13050     DisplayTitle(buf);
13051     if (first.sendName) {
13052       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13053       SendToProgram(buf, &first);
13054     }
13055     if (first.sendTime) {
13056       if (first.useColors) {
13057         SendToProgram("white\n", &first); /*gnu kludge*/
13058       }
13059       SendTimeRemaining(&first, FALSE);
13060     }
13061     if (first.useColors) {
13062       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13063     }
13064     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13065     SetMachineThinkingEnables();
13066     first.maybeThinking = TRUE;
13067     StartClocks();
13068
13069     if (appData.autoFlipView && flipView) {
13070       flipView = !flipView;
13071       DrawPosition(FALSE, NULL);
13072       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13073     }
13074     if(bookHit) { // [HGM] book: simulate book reply
13075         static char bookMove[MSG_SIZ]; // a bit generous?
13076
13077         programStats.nodes = programStats.depth = programStats.time =
13078         programStats.score = programStats.got_only_move = 0;
13079         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13080
13081         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13082         strcat(bookMove, bookHit);
13083         HandleMachineMove(bookMove, &first);
13084     }
13085 }
13086
13087
13088 void
13089 DisplayTwoMachinesTitle()
13090 {
13091     char buf[MSG_SIZ];
13092     if (appData.matchGames > 0) {
13093         if(appData.tourneyFile[0]) {
13094           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13095                    gameInfo.white, gameInfo.black,
13096                    nextGame+1, appData.matchGames+1,
13097                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13098         } else 
13099         if (first.twoMachinesColor[0] == 'w') {
13100           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13101                    gameInfo.white, gameInfo.black,
13102                    first.matchWins, second.matchWins,
13103                    matchGame - 1 - (first.matchWins + second.matchWins));
13104         } else {
13105           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13106                    gameInfo.white, gameInfo.black,
13107                    second.matchWins, first.matchWins,
13108                    matchGame - 1 - (first.matchWins + second.matchWins));
13109         }
13110     } else {
13111       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13112     }
13113     DisplayTitle(buf);
13114 }
13115
13116 void
13117 SettingsMenuIfReady()
13118 {
13119   if (second.lastPing != second.lastPong) {
13120     DisplayMessage("", _("Waiting for second chess program"));
13121     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13122     return;
13123   }
13124   ThawUI();
13125   DisplayMessage("", "");
13126   SettingsPopUp(&second);
13127 }
13128
13129 int
13130 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13131 {
13132     char buf[MSG_SIZ];
13133     if (cps->pr == NULL) {
13134         StartChessProgram(cps);
13135         if (cps->protocolVersion == 1) {
13136           retry();
13137         } else {
13138           /* kludge: allow timeout for initial "feature" command */
13139           FreezeUI();
13140           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13141           DisplayMessage("", buf);
13142           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13143         }
13144         return 1;
13145     }
13146     return 0;
13147 }
13148
13149 void
13150 TwoMachinesEvent P((void))
13151 {
13152     int i;
13153     char buf[MSG_SIZ];
13154     ChessProgramState *onmove;
13155     char *bookHit = NULL;
13156     static int stalling = 0;
13157     TimeMark now;
13158     long wait;
13159
13160     if (appData.noChessProgram) return;
13161
13162     switch (gameMode) {
13163       case TwoMachinesPlay:
13164         return;
13165       case MachinePlaysWhite:
13166       case MachinePlaysBlack:
13167         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13168             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13169             return;
13170         }
13171         /* fall through */
13172       case BeginningOfGame:
13173       case PlayFromGameFile:
13174       case EndOfGame:
13175         EditGameEvent();
13176         if (gameMode != EditGame) return;
13177         break;
13178       case EditPosition:
13179         EditPositionDone(TRUE);
13180         break;
13181       case AnalyzeMode:
13182       case AnalyzeFile:
13183         ExitAnalyzeMode();
13184         break;
13185       case EditGame:
13186       default:
13187         break;
13188     }
13189
13190 //    forwardMostMove = currentMove;
13191     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13192
13193     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13194
13195     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13196     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13197       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13198       return;
13199     }
13200     if(!stalling) {
13201       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13202       SendToProgram("force\n", &second);
13203       stalling = 1;
13204       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13205       return;
13206     }
13207     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13208     if(appData.matchPause>10000 || appData.matchPause<10)
13209                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13210     wait = SubtractTimeMarks(&now, &pauseStart);
13211     if(wait < appData.matchPause) {
13212         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13213         return;
13214     }
13215     stalling = 0;
13216     DisplayMessage("", "");
13217     if (startedFromSetupPosition) {
13218         SendBoard(&second, backwardMostMove);
13219     if (appData.debugMode) {
13220         fprintf(debugFP, "Two Machines\n");
13221     }
13222     }
13223     for (i = backwardMostMove; i < forwardMostMove; i++) {
13224         SendMoveToProgram(i, &second);
13225     }
13226
13227     gameMode = TwoMachinesPlay;
13228     pausing = FALSE;
13229     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13230     SetGameInfo();
13231     DisplayTwoMachinesTitle();
13232     firstMove = TRUE;
13233     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13234         onmove = &first;
13235     } else {
13236         onmove = &second;
13237     }
13238     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13239     SendToProgram(first.computerString, &first);
13240     if (first.sendName) {
13241       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13242       SendToProgram(buf, &first);
13243     }
13244     SendToProgram(second.computerString, &second);
13245     if (second.sendName) {
13246       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13247       SendToProgram(buf, &second);
13248     }
13249
13250     ResetClocks();
13251     if (!first.sendTime || !second.sendTime) {
13252         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13253         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13254     }
13255     if (onmove->sendTime) {
13256       if (onmove->useColors) {
13257         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13258       }
13259       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13260     }
13261     if (onmove->useColors) {
13262       SendToProgram(onmove->twoMachinesColor, onmove);
13263     }
13264     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13265 //    SendToProgram("go\n", onmove);
13266     onmove->maybeThinking = TRUE;
13267     SetMachineThinkingEnables();
13268
13269     StartClocks();
13270
13271     if(bookHit) { // [HGM] book: simulate book reply
13272         static char bookMove[MSG_SIZ]; // a bit generous?
13273
13274         programStats.nodes = programStats.depth = programStats.time =
13275         programStats.score = programStats.got_only_move = 0;
13276         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13277
13278         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13279         strcat(bookMove, bookHit);
13280         savedMessage = bookMove; // args for deferred call
13281         savedState = onmove;
13282         ScheduleDelayedEvent(DeferredBookMove, 1);
13283     }
13284 }
13285
13286 void
13287 TrainingEvent()
13288 {
13289     if (gameMode == Training) {
13290       SetTrainingModeOff();
13291       gameMode = PlayFromGameFile;
13292       DisplayMessage("", _("Training mode off"));
13293     } else {
13294       gameMode = Training;
13295       animateTraining = appData.animate;
13296
13297       /* make sure we are not already at the end of the game */
13298       if (currentMove < forwardMostMove) {
13299         SetTrainingModeOn();
13300         DisplayMessage("", _("Training mode on"));
13301       } else {
13302         gameMode = PlayFromGameFile;
13303         DisplayError(_("Already at end of game"), 0);
13304       }
13305     }
13306     ModeHighlight();
13307 }
13308
13309 void
13310 IcsClientEvent()
13311 {
13312     if (!appData.icsActive) return;
13313     switch (gameMode) {
13314       case IcsPlayingWhite:
13315       case IcsPlayingBlack:
13316       case IcsObserving:
13317       case IcsIdle:
13318       case BeginningOfGame:
13319       case IcsExamining:
13320         return;
13321
13322       case EditGame:
13323         break;
13324
13325       case EditPosition:
13326         EditPositionDone(TRUE);
13327         break;
13328
13329       case AnalyzeMode:
13330       case AnalyzeFile:
13331         ExitAnalyzeMode();
13332         break;
13333
13334       default:
13335         EditGameEvent();
13336         break;
13337     }
13338
13339     gameMode = IcsIdle;
13340     ModeHighlight();
13341     return;
13342 }
13343
13344
13345 void
13346 EditGameEvent()
13347 {
13348     int i;
13349
13350     switch (gameMode) {
13351       case Training:
13352         SetTrainingModeOff();
13353         break;
13354       case MachinePlaysWhite:
13355       case MachinePlaysBlack:
13356       case BeginningOfGame:
13357         SendToProgram("force\n", &first);
13358         SetUserThinkingEnables();
13359         break;
13360       case PlayFromGameFile:
13361         (void) StopLoadGameTimer();
13362         if (gameFileFP != NULL) {
13363             gameFileFP = NULL;
13364         }
13365         break;
13366       case EditPosition:
13367         EditPositionDone(TRUE);
13368         break;
13369       case AnalyzeMode:
13370       case AnalyzeFile:
13371         ExitAnalyzeMode();
13372         SendToProgram("force\n", &first);
13373         break;
13374       case TwoMachinesPlay:
13375         GameEnds(EndOfFile, NULL, GE_PLAYER);
13376         ResurrectChessProgram();
13377         SetUserThinkingEnables();
13378         break;
13379       case EndOfGame:
13380         ResurrectChessProgram();
13381         break;
13382       case IcsPlayingBlack:
13383       case IcsPlayingWhite:
13384         DisplayError(_("Warning: You are still playing a game"), 0);
13385         break;
13386       case IcsObserving:
13387         DisplayError(_("Warning: You are still observing a game"), 0);
13388         break;
13389       case IcsExamining:
13390         DisplayError(_("Warning: You are still examining a game"), 0);
13391         break;
13392       case IcsIdle:
13393         break;
13394       case EditGame:
13395       default:
13396         return;
13397     }
13398
13399     pausing = FALSE;
13400     StopClocks();
13401     first.offeredDraw = second.offeredDraw = 0;
13402
13403     if (gameMode == PlayFromGameFile) {
13404         whiteTimeRemaining = timeRemaining[0][currentMove];
13405         blackTimeRemaining = timeRemaining[1][currentMove];
13406         DisplayTitle("");
13407     }
13408
13409     if (gameMode == MachinePlaysWhite ||
13410         gameMode == MachinePlaysBlack ||
13411         gameMode == TwoMachinesPlay ||
13412         gameMode == EndOfGame) {
13413         i = forwardMostMove;
13414         while (i > currentMove) {
13415             SendToProgram("undo\n", &first);
13416             i--;
13417         }
13418         whiteTimeRemaining = timeRemaining[0][currentMove];
13419         blackTimeRemaining = timeRemaining[1][currentMove];
13420         DisplayBothClocks();
13421         if (whiteFlag || blackFlag) {
13422             whiteFlag = blackFlag = 0;
13423         }
13424         DisplayTitle("");
13425     }
13426
13427     gameMode = EditGame;
13428     ModeHighlight();
13429     SetGameInfo();
13430 }
13431
13432
13433 void
13434 EditPositionEvent()
13435 {
13436     if (gameMode == EditPosition) {
13437         EditGameEvent();
13438         return;
13439     }
13440
13441     EditGameEvent();
13442     if (gameMode != EditGame) return;
13443
13444     gameMode = EditPosition;
13445     ModeHighlight();
13446     SetGameInfo();
13447     if (currentMove > 0)
13448       CopyBoard(boards[0], boards[currentMove]);
13449
13450     blackPlaysFirst = !WhiteOnMove(currentMove);
13451     ResetClocks();
13452     currentMove = forwardMostMove = backwardMostMove = 0;
13453     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13454     DisplayMove(-1);
13455 }
13456
13457 void
13458 ExitAnalyzeMode()
13459 {
13460     /* [DM] icsEngineAnalyze - possible call from other functions */
13461     if (appData.icsEngineAnalyze) {
13462         appData.icsEngineAnalyze = FALSE;
13463
13464         DisplayMessage("",_("Close ICS engine analyze..."));
13465     }
13466     if (first.analysisSupport && first.analyzing) {
13467       SendToProgram("exit\n", &first);
13468       first.analyzing = FALSE;
13469     }
13470     thinkOutput[0] = NULLCHAR;
13471 }
13472
13473 void
13474 EditPositionDone(Boolean fakeRights)
13475 {
13476     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13477
13478     startedFromSetupPosition = TRUE;
13479     InitChessProgram(&first, FALSE);
13480     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13481       boards[0][EP_STATUS] = EP_NONE;
13482       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13483     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13484         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13485         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13486       } else boards[0][CASTLING][2] = NoRights;
13487     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13488         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13489         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13490       } else boards[0][CASTLING][5] = NoRights;
13491     }
13492     SendToProgram("force\n", &first);
13493     if (blackPlaysFirst) {
13494         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13495         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13496         currentMove = forwardMostMove = backwardMostMove = 1;
13497         CopyBoard(boards[1], boards[0]);
13498     } else {
13499         currentMove = forwardMostMove = backwardMostMove = 0;
13500     }
13501     SendBoard(&first, forwardMostMove);
13502     if (appData.debugMode) {
13503         fprintf(debugFP, "EditPosDone\n");
13504     }
13505     DisplayTitle("");
13506     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13507     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13508     gameMode = EditGame;
13509     ModeHighlight();
13510     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13511     ClearHighlights(); /* [AS] */
13512 }
13513
13514 /* Pause for `ms' milliseconds */
13515 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13516 void
13517 TimeDelay(ms)
13518      long ms;
13519 {
13520     TimeMark m1, m2;
13521
13522     GetTimeMark(&m1);
13523     do {
13524         GetTimeMark(&m2);
13525     } while (SubtractTimeMarks(&m2, &m1) < ms);
13526 }
13527
13528 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13529 void
13530 SendMultiLineToICS(buf)
13531      char *buf;
13532 {
13533     char temp[MSG_SIZ+1], *p;
13534     int len;
13535
13536     len = strlen(buf);
13537     if (len > MSG_SIZ)
13538       len = MSG_SIZ;
13539
13540     strncpy(temp, buf, len);
13541     temp[len] = 0;
13542
13543     p = temp;
13544     while (*p) {
13545         if (*p == '\n' || *p == '\r')
13546           *p = ' ';
13547         ++p;
13548     }
13549
13550     strcat(temp, "\n");
13551     SendToICS(temp);
13552     SendToPlayer(temp, strlen(temp));
13553 }
13554
13555 void
13556 SetWhiteToPlayEvent()
13557 {
13558     if (gameMode == EditPosition) {
13559         blackPlaysFirst = FALSE;
13560         DisplayBothClocks();    /* works because currentMove is 0 */
13561     } else if (gameMode == IcsExamining) {
13562         SendToICS(ics_prefix);
13563         SendToICS("tomove white\n");
13564     }
13565 }
13566
13567 void
13568 SetBlackToPlayEvent()
13569 {
13570     if (gameMode == EditPosition) {
13571         blackPlaysFirst = TRUE;
13572         currentMove = 1;        /* kludge */
13573         DisplayBothClocks();
13574         currentMove = 0;
13575     } else if (gameMode == IcsExamining) {
13576         SendToICS(ics_prefix);
13577         SendToICS("tomove black\n");
13578     }
13579 }
13580
13581 void
13582 EditPositionMenuEvent(selection, x, y)
13583      ChessSquare selection;
13584      int x, y;
13585 {
13586     char buf[MSG_SIZ];
13587     ChessSquare piece = boards[0][y][x];
13588
13589     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13590
13591     switch (selection) {
13592       case ClearBoard:
13593         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13594             SendToICS(ics_prefix);
13595             SendToICS("bsetup clear\n");
13596         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13597             SendToICS(ics_prefix);
13598             SendToICS("clearboard\n");
13599         } else {
13600             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13601                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13602                 for (y = 0; y < BOARD_HEIGHT; y++) {
13603                     if (gameMode == IcsExamining) {
13604                         if (boards[currentMove][y][x] != EmptySquare) {
13605                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13606                                     AAA + x, ONE + y);
13607                             SendToICS(buf);
13608                         }
13609                     } else {
13610                         boards[0][y][x] = p;
13611                     }
13612                 }
13613             }
13614         }
13615         if (gameMode == EditPosition) {
13616             DrawPosition(FALSE, boards[0]);
13617         }
13618         break;
13619
13620       case WhitePlay:
13621         SetWhiteToPlayEvent();
13622         break;
13623
13624       case BlackPlay:
13625         SetBlackToPlayEvent();
13626         break;
13627
13628       case EmptySquare:
13629         if (gameMode == IcsExamining) {
13630             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13631             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13632             SendToICS(buf);
13633         } else {
13634             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13635                 if(x == BOARD_LEFT-2) {
13636                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13637                     boards[0][y][1] = 0;
13638                 } else
13639                 if(x == BOARD_RGHT+1) {
13640                     if(y >= gameInfo.holdingsSize) break;
13641                     boards[0][y][BOARD_WIDTH-2] = 0;
13642                 } else break;
13643             }
13644             boards[0][y][x] = EmptySquare;
13645             DrawPosition(FALSE, boards[0]);
13646         }
13647         break;
13648
13649       case PromotePiece:
13650         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13651            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13652             selection = (ChessSquare) (PROMOTED piece);
13653         } else if(piece == EmptySquare) selection = WhiteSilver;
13654         else selection = (ChessSquare)((int)piece - 1);
13655         goto defaultlabel;
13656
13657       case DemotePiece:
13658         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13659            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13660             selection = (ChessSquare) (DEMOTED piece);
13661         } else if(piece == EmptySquare) selection = BlackSilver;
13662         else selection = (ChessSquare)((int)piece + 1);
13663         goto defaultlabel;
13664
13665       case WhiteQueen:
13666       case BlackQueen:
13667         if(gameInfo.variant == VariantShatranj ||
13668            gameInfo.variant == VariantXiangqi  ||
13669            gameInfo.variant == VariantCourier  ||
13670            gameInfo.variant == VariantMakruk     )
13671             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13672         goto defaultlabel;
13673
13674       case WhiteKing:
13675       case BlackKing:
13676         if(gameInfo.variant == VariantXiangqi)
13677             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13678         if(gameInfo.variant == VariantKnightmate)
13679             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13680       default:
13681         defaultlabel:
13682         if (gameMode == IcsExamining) {
13683             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13684             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13685                      PieceToChar(selection), AAA + x, ONE + y);
13686             SendToICS(buf);
13687         } else {
13688             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13689                 int n;
13690                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13691                     n = PieceToNumber(selection - BlackPawn);
13692                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13693                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13694                     boards[0][BOARD_HEIGHT-1-n][1]++;
13695                 } else
13696                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13697                     n = PieceToNumber(selection);
13698                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13699                     boards[0][n][BOARD_WIDTH-1] = selection;
13700                     boards[0][n][BOARD_WIDTH-2]++;
13701                 }
13702             } else
13703             boards[0][y][x] = selection;
13704             DrawPosition(TRUE, boards[0]);
13705         }
13706         break;
13707     }
13708 }
13709
13710
13711 void
13712 DropMenuEvent(selection, x, y)
13713      ChessSquare selection;
13714      int x, y;
13715 {
13716     ChessMove moveType;
13717
13718     switch (gameMode) {
13719       case IcsPlayingWhite:
13720       case MachinePlaysBlack:
13721         if (!WhiteOnMove(currentMove)) {
13722             DisplayMoveError(_("It is Black's turn"));
13723             return;
13724         }
13725         moveType = WhiteDrop;
13726         break;
13727       case IcsPlayingBlack:
13728       case MachinePlaysWhite:
13729         if (WhiteOnMove(currentMove)) {
13730             DisplayMoveError(_("It is White's turn"));
13731             return;
13732         }
13733         moveType = BlackDrop;
13734         break;
13735       case EditGame:
13736         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13737         break;
13738       default:
13739         return;
13740     }
13741
13742     if (moveType == BlackDrop && selection < BlackPawn) {
13743       selection = (ChessSquare) ((int) selection
13744                                  + (int) BlackPawn - (int) WhitePawn);
13745     }
13746     if (boards[currentMove][y][x] != EmptySquare) {
13747         DisplayMoveError(_("That square is occupied"));
13748         return;
13749     }
13750
13751     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13752 }
13753
13754 void
13755 AcceptEvent()
13756 {
13757     /* Accept a pending offer of any kind from opponent */
13758
13759     if (appData.icsActive) {
13760         SendToICS(ics_prefix);
13761         SendToICS("accept\n");
13762     } else if (cmailMsgLoaded) {
13763         if (currentMove == cmailOldMove &&
13764             commentList[cmailOldMove] != NULL &&
13765             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13766                    "Black offers a draw" : "White offers a draw")) {
13767             TruncateGame();
13768             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13769             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13770         } else {
13771             DisplayError(_("There is no pending offer on this move"), 0);
13772             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13773         }
13774     } else {
13775         /* Not used for offers from chess program */
13776     }
13777 }
13778
13779 void
13780 DeclineEvent()
13781 {
13782     /* Decline a pending offer of any kind from opponent */
13783
13784     if (appData.icsActive) {
13785         SendToICS(ics_prefix);
13786         SendToICS("decline\n");
13787     } else if (cmailMsgLoaded) {
13788         if (currentMove == cmailOldMove &&
13789             commentList[cmailOldMove] != NULL &&
13790             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13791                    "Black offers a draw" : "White offers a draw")) {
13792 #ifdef NOTDEF
13793             AppendComment(cmailOldMove, "Draw declined", TRUE);
13794             DisplayComment(cmailOldMove - 1, "Draw declined");
13795 #endif /*NOTDEF*/
13796         } else {
13797             DisplayError(_("There is no pending offer on this move"), 0);
13798         }
13799     } else {
13800         /* Not used for offers from chess program */
13801     }
13802 }
13803
13804 void
13805 RematchEvent()
13806 {
13807     /* Issue ICS rematch command */
13808     if (appData.icsActive) {
13809         SendToICS(ics_prefix);
13810         SendToICS("rematch\n");
13811     }
13812 }
13813
13814 void
13815 CallFlagEvent()
13816 {
13817     /* Call your opponent's flag (claim a win on time) */
13818     if (appData.icsActive) {
13819         SendToICS(ics_prefix);
13820         SendToICS("flag\n");
13821     } else {
13822         switch (gameMode) {
13823           default:
13824             return;
13825           case MachinePlaysWhite:
13826             if (whiteFlag) {
13827                 if (blackFlag)
13828                   GameEnds(GameIsDrawn, "Both players ran out of time",
13829                            GE_PLAYER);
13830                 else
13831                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13832             } else {
13833                 DisplayError(_("Your opponent is not out of time"), 0);
13834             }
13835             break;
13836           case MachinePlaysBlack:
13837             if (blackFlag) {
13838                 if (whiteFlag)
13839                   GameEnds(GameIsDrawn, "Both players ran out of time",
13840                            GE_PLAYER);
13841                 else
13842                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13843             } else {
13844                 DisplayError(_("Your opponent is not out of time"), 0);
13845             }
13846             break;
13847         }
13848     }
13849 }
13850
13851 void
13852 ClockClick(int which)
13853 {       // [HGM] code moved to back-end from winboard.c
13854         if(which) { // black clock
13855           if (gameMode == EditPosition || gameMode == IcsExamining) {
13856             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13857             SetBlackToPlayEvent();
13858           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13859           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13860           } else if (shiftKey) {
13861             AdjustClock(which, -1);
13862           } else if (gameMode == IcsPlayingWhite ||
13863                      gameMode == MachinePlaysBlack) {
13864             CallFlagEvent();
13865           }
13866         } else { // white clock
13867           if (gameMode == EditPosition || gameMode == IcsExamining) {
13868             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13869             SetWhiteToPlayEvent();
13870           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13871           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13872           } else if (shiftKey) {
13873             AdjustClock(which, -1);
13874           } else if (gameMode == IcsPlayingBlack ||
13875                    gameMode == MachinePlaysWhite) {
13876             CallFlagEvent();
13877           }
13878         }
13879 }
13880
13881 void
13882 DrawEvent()
13883 {
13884     /* Offer draw or accept pending draw offer from opponent */
13885
13886     if (appData.icsActive) {
13887         /* Note: tournament rules require draw offers to be
13888            made after you make your move but before you punch
13889            your clock.  Currently ICS doesn't let you do that;
13890            instead, you immediately punch your clock after making
13891            a move, but you can offer a draw at any time. */
13892
13893         SendToICS(ics_prefix);
13894         SendToICS("draw\n");
13895         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13896     } else if (cmailMsgLoaded) {
13897         if (currentMove == cmailOldMove &&
13898             commentList[cmailOldMove] != NULL &&
13899             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13900                    "Black offers a draw" : "White offers a draw")) {
13901             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13902             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13903         } else if (currentMove == cmailOldMove + 1) {
13904             char *offer = WhiteOnMove(cmailOldMove) ?
13905               "White offers a draw" : "Black offers a draw";
13906             AppendComment(currentMove, offer, TRUE);
13907             DisplayComment(currentMove - 1, offer);
13908             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13909         } else {
13910             DisplayError(_("You must make your move before offering a draw"), 0);
13911             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13912         }
13913     } else if (first.offeredDraw) {
13914         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13915     } else {
13916         if (first.sendDrawOffers) {
13917             SendToProgram("draw\n", &first);
13918             userOfferedDraw = TRUE;
13919         }
13920     }
13921 }
13922
13923 void
13924 AdjournEvent()
13925 {
13926     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13927
13928     if (appData.icsActive) {
13929         SendToICS(ics_prefix);
13930         SendToICS("adjourn\n");
13931     } else {
13932         /* Currently GNU Chess doesn't offer or accept Adjourns */
13933     }
13934 }
13935
13936
13937 void
13938 AbortEvent()
13939 {
13940     /* Offer Abort or accept pending Abort offer from opponent */
13941
13942     if (appData.icsActive) {
13943         SendToICS(ics_prefix);
13944         SendToICS("abort\n");
13945     } else {
13946         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13947     }
13948 }
13949
13950 void
13951 ResignEvent()
13952 {
13953     /* Resign.  You can do this even if it's not your turn. */
13954
13955     if (appData.icsActive) {
13956         SendToICS(ics_prefix);
13957         SendToICS("resign\n");
13958     } else {
13959         switch (gameMode) {
13960           case MachinePlaysWhite:
13961             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13962             break;
13963           case MachinePlaysBlack:
13964             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13965             break;
13966           case EditGame:
13967             if (cmailMsgLoaded) {
13968                 TruncateGame();
13969                 if (WhiteOnMove(cmailOldMove)) {
13970                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13971                 } else {
13972                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13973                 }
13974                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13975             }
13976             break;
13977           default:
13978             break;
13979         }
13980     }
13981 }
13982
13983
13984 void
13985 StopObservingEvent()
13986 {
13987     /* Stop observing current games */
13988     SendToICS(ics_prefix);
13989     SendToICS("unobserve\n");
13990 }
13991
13992 void
13993 StopExaminingEvent()
13994 {
13995     /* Stop observing current game */
13996     SendToICS(ics_prefix);
13997     SendToICS("unexamine\n");
13998 }
13999
14000 void
14001 ForwardInner(target)
14002      int target;
14003 {
14004     int limit;
14005
14006     if (appData.debugMode)
14007         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14008                 target, currentMove, forwardMostMove);
14009
14010     if (gameMode == EditPosition)
14011       return;
14012
14013     if (gameMode == PlayFromGameFile && !pausing)
14014       PauseEvent();
14015
14016     if (gameMode == IcsExamining && pausing)
14017       limit = pauseExamForwardMostMove;
14018     else
14019       limit = forwardMostMove;
14020
14021     if (target > limit) target = limit;
14022
14023     if (target > 0 && moveList[target - 1][0]) {
14024         int fromX, fromY, toX, toY;
14025         toX = moveList[target - 1][2] - AAA;
14026         toY = moveList[target - 1][3] - ONE;
14027         if (moveList[target - 1][1] == '@') {
14028             if (appData.highlightLastMove) {
14029                 SetHighlights(-1, -1, toX, toY);
14030             }
14031         } else {
14032             fromX = moveList[target - 1][0] - AAA;
14033             fromY = moveList[target - 1][1] - ONE;
14034             if (target == currentMove + 1) {
14035                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14036             }
14037             if (appData.highlightLastMove) {
14038                 SetHighlights(fromX, fromY, toX, toY);
14039             }
14040         }
14041     }
14042     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14043         gameMode == Training || gameMode == PlayFromGameFile ||
14044         gameMode == AnalyzeFile) {
14045         while (currentMove < target) {
14046             SendMoveToProgram(currentMove++, &first);
14047         }
14048     } else {
14049         currentMove = target;
14050     }
14051
14052     if (gameMode == EditGame || gameMode == EndOfGame) {
14053         whiteTimeRemaining = timeRemaining[0][currentMove];
14054         blackTimeRemaining = timeRemaining[1][currentMove];
14055     }
14056     DisplayBothClocks();
14057     DisplayMove(currentMove - 1);
14058     DrawPosition(FALSE, boards[currentMove]);
14059     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14060     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14061         DisplayComment(currentMove - 1, commentList[currentMove]);
14062     }
14063 }
14064
14065
14066 void
14067 ForwardEvent()
14068 {
14069     if (gameMode == IcsExamining && !pausing) {
14070         SendToICS(ics_prefix);
14071         SendToICS("forward\n");
14072     } else {
14073         ForwardInner(currentMove + 1);
14074     }
14075 }
14076
14077 void
14078 ToEndEvent()
14079 {
14080     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14081         /* to optimze, we temporarily turn off analysis mode while we feed
14082          * the remaining moves to the engine. Otherwise we get analysis output
14083          * after each move.
14084          */
14085         if (first.analysisSupport) {
14086           SendToProgram("exit\nforce\n", &first);
14087           first.analyzing = FALSE;
14088         }
14089     }
14090
14091     if (gameMode == IcsExamining && !pausing) {
14092         SendToICS(ics_prefix);
14093         SendToICS("forward 999999\n");
14094     } else {
14095         ForwardInner(forwardMostMove);
14096     }
14097
14098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14099         /* we have fed all the moves, so reactivate analysis mode */
14100         SendToProgram("analyze\n", &first);
14101         first.analyzing = TRUE;
14102         /*first.maybeThinking = TRUE;*/
14103         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14104     }
14105 }
14106
14107 void
14108 BackwardInner(target)
14109      int target;
14110 {
14111     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14112
14113     if (appData.debugMode)
14114         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14115                 target, currentMove, forwardMostMove);
14116
14117     if (gameMode == EditPosition) return;
14118     if (currentMove <= backwardMostMove) {
14119         ClearHighlights();
14120         DrawPosition(full_redraw, boards[currentMove]);
14121         return;
14122     }
14123     if (gameMode == PlayFromGameFile && !pausing)
14124       PauseEvent();
14125
14126     if (moveList[target][0]) {
14127         int fromX, fromY, toX, toY;
14128         toX = moveList[target][2] - AAA;
14129         toY = moveList[target][3] - ONE;
14130         if (moveList[target][1] == '@') {
14131             if (appData.highlightLastMove) {
14132                 SetHighlights(-1, -1, toX, toY);
14133             }
14134         } else {
14135             fromX = moveList[target][0] - AAA;
14136             fromY = moveList[target][1] - ONE;
14137             if (target == currentMove - 1) {
14138                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14139             }
14140             if (appData.highlightLastMove) {
14141                 SetHighlights(fromX, fromY, toX, toY);
14142             }
14143         }
14144     }
14145     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14146         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14147         while (currentMove > target) {
14148             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14149                 // null move cannot be undone. Reload program with move history before it.
14150                 int i;
14151                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14152                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14153                 }
14154                 SendBoard(&first, i); 
14155                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14156                 break;
14157             }
14158             SendToProgram("undo\n", &first);
14159             currentMove--;
14160         }
14161     } else {
14162         currentMove = target;
14163     }
14164
14165     if (gameMode == EditGame || gameMode == EndOfGame) {
14166         whiteTimeRemaining = timeRemaining[0][currentMove];
14167         blackTimeRemaining = timeRemaining[1][currentMove];
14168     }
14169     DisplayBothClocks();
14170     DisplayMove(currentMove - 1);
14171     DrawPosition(full_redraw, boards[currentMove]);
14172     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14173     // [HGM] PV info: routine tests if comment empty
14174     DisplayComment(currentMove - 1, commentList[currentMove]);
14175 }
14176
14177 void
14178 BackwardEvent()
14179 {
14180     if (gameMode == IcsExamining && !pausing) {
14181         SendToICS(ics_prefix);
14182         SendToICS("backward\n");
14183     } else {
14184         BackwardInner(currentMove - 1);
14185     }
14186 }
14187
14188 void
14189 ToStartEvent()
14190 {
14191     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14192         /* to optimize, we temporarily turn off analysis mode while we undo
14193          * all the moves. Otherwise we get analysis output after each undo.
14194          */
14195         if (first.analysisSupport) {
14196           SendToProgram("exit\nforce\n", &first);
14197           first.analyzing = FALSE;
14198         }
14199     }
14200
14201     if (gameMode == IcsExamining && !pausing) {
14202         SendToICS(ics_prefix);
14203         SendToICS("backward 999999\n");
14204     } else {
14205         BackwardInner(backwardMostMove);
14206     }
14207
14208     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14209         /* we have fed all the moves, so reactivate analysis mode */
14210         SendToProgram("analyze\n", &first);
14211         first.analyzing = TRUE;
14212         /*first.maybeThinking = TRUE;*/
14213         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14214     }
14215 }
14216
14217 void
14218 ToNrEvent(int to)
14219 {
14220   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14221   if (to >= forwardMostMove) to = forwardMostMove;
14222   if (to <= backwardMostMove) to = backwardMostMove;
14223   if (to < currentMove) {
14224     BackwardInner(to);
14225   } else {
14226     ForwardInner(to);
14227   }
14228 }
14229
14230 void
14231 RevertEvent(Boolean annotate)
14232 {
14233     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14234         return;
14235     }
14236     if (gameMode != IcsExamining) {
14237         DisplayError(_("You are not examining a game"), 0);
14238         return;
14239     }
14240     if (pausing) {
14241         DisplayError(_("You can't revert while pausing"), 0);
14242         return;
14243     }
14244     SendToICS(ics_prefix);
14245     SendToICS("revert\n");
14246 }
14247
14248 void
14249 RetractMoveEvent()
14250 {
14251     switch (gameMode) {
14252       case MachinePlaysWhite:
14253       case MachinePlaysBlack:
14254         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14255             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14256             return;
14257         }
14258         if (forwardMostMove < 2) return;
14259         currentMove = forwardMostMove = forwardMostMove - 2;
14260         whiteTimeRemaining = timeRemaining[0][currentMove];
14261         blackTimeRemaining = timeRemaining[1][currentMove];
14262         DisplayBothClocks();
14263         DisplayMove(currentMove - 1);
14264         ClearHighlights();/*!! could figure this out*/
14265         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14266         SendToProgram("remove\n", &first);
14267         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14268         break;
14269
14270       case BeginningOfGame:
14271       default:
14272         break;
14273
14274       case IcsPlayingWhite:
14275       case IcsPlayingBlack:
14276         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14277             SendToICS(ics_prefix);
14278             SendToICS("takeback 2\n");
14279         } else {
14280             SendToICS(ics_prefix);
14281             SendToICS("takeback 1\n");
14282         }
14283         break;
14284     }
14285 }
14286
14287 void
14288 MoveNowEvent()
14289 {
14290     ChessProgramState *cps;
14291
14292     switch (gameMode) {
14293       case MachinePlaysWhite:
14294         if (!WhiteOnMove(forwardMostMove)) {
14295             DisplayError(_("It is your turn"), 0);
14296             return;
14297         }
14298         cps = &first;
14299         break;
14300       case MachinePlaysBlack:
14301         if (WhiteOnMove(forwardMostMove)) {
14302             DisplayError(_("It is your turn"), 0);
14303             return;
14304         }
14305         cps = &first;
14306         break;
14307       case TwoMachinesPlay:
14308         if (WhiteOnMove(forwardMostMove) ==
14309             (first.twoMachinesColor[0] == 'w')) {
14310             cps = &first;
14311         } else {
14312             cps = &second;
14313         }
14314         break;
14315       case BeginningOfGame:
14316       default:
14317         return;
14318     }
14319     SendToProgram("?\n", cps);
14320 }
14321
14322 void
14323 TruncateGameEvent()
14324 {
14325     EditGameEvent();
14326     if (gameMode != EditGame) return;
14327     TruncateGame();
14328 }
14329
14330 void
14331 TruncateGame()
14332 {
14333     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14334     if (forwardMostMove > currentMove) {
14335         if (gameInfo.resultDetails != NULL) {
14336             free(gameInfo.resultDetails);
14337             gameInfo.resultDetails = NULL;
14338             gameInfo.result = GameUnfinished;
14339         }
14340         forwardMostMove = currentMove;
14341         HistorySet(parseList, backwardMostMove, forwardMostMove,
14342                    currentMove-1);
14343     }
14344 }
14345
14346 void
14347 HintEvent()
14348 {
14349     if (appData.noChessProgram) return;
14350     switch (gameMode) {
14351       case MachinePlaysWhite:
14352         if (WhiteOnMove(forwardMostMove)) {
14353             DisplayError(_("Wait until your turn"), 0);
14354             return;
14355         }
14356         break;
14357       case BeginningOfGame:
14358       case MachinePlaysBlack:
14359         if (!WhiteOnMove(forwardMostMove)) {
14360             DisplayError(_("Wait until your turn"), 0);
14361             return;
14362         }
14363         break;
14364       default:
14365         DisplayError(_("No hint available"), 0);
14366         return;
14367     }
14368     SendToProgram("hint\n", &first);
14369     hintRequested = TRUE;
14370 }
14371
14372 void
14373 BookEvent()
14374 {
14375     if (appData.noChessProgram) return;
14376     switch (gameMode) {
14377       case MachinePlaysWhite:
14378         if (WhiteOnMove(forwardMostMove)) {
14379             DisplayError(_("Wait until your turn"), 0);
14380             return;
14381         }
14382         break;
14383       case BeginningOfGame:
14384       case MachinePlaysBlack:
14385         if (!WhiteOnMove(forwardMostMove)) {
14386             DisplayError(_("Wait until your turn"), 0);
14387             return;
14388         }
14389         break;
14390       case EditPosition:
14391         EditPositionDone(TRUE);
14392         break;
14393       case TwoMachinesPlay:
14394         return;
14395       default:
14396         break;
14397     }
14398     SendToProgram("bk\n", &first);
14399     bookOutput[0] = NULLCHAR;
14400     bookRequested = TRUE;
14401 }
14402
14403 void
14404 AboutGameEvent()
14405 {
14406     char *tags = PGNTags(&gameInfo);
14407     TagsPopUp(tags, CmailMsg());
14408     free(tags);
14409 }
14410
14411 /* end button procedures */
14412
14413 void
14414 PrintPosition(fp, move)
14415      FILE *fp;
14416      int move;
14417 {
14418     int i, j;
14419
14420     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14421         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14422             char c = PieceToChar(boards[move][i][j]);
14423             fputc(c == 'x' ? '.' : c, fp);
14424             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14425         }
14426     }
14427     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14428       fprintf(fp, "white to play\n");
14429     else
14430       fprintf(fp, "black to play\n");
14431 }
14432
14433 void
14434 PrintOpponents(fp)
14435      FILE *fp;
14436 {
14437     if (gameInfo.white != NULL) {
14438         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14439     } else {
14440         fprintf(fp, "\n");
14441     }
14442 }
14443
14444 /* Find last component of program's own name, using some heuristics */
14445 void
14446 TidyProgramName(prog, host, buf)
14447      char *prog, *host, buf[MSG_SIZ];
14448 {
14449     char *p, *q;
14450     int local = (strcmp(host, "localhost") == 0);
14451     while (!local && (p = strchr(prog, ';')) != NULL) {
14452         p++;
14453         while (*p == ' ') p++;
14454         prog = p;
14455     }
14456     if (*prog == '"' || *prog == '\'') {
14457         q = strchr(prog + 1, *prog);
14458     } else {
14459         q = strchr(prog, ' ');
14460     }
14461     if (q == NULL) q = prog + strlen(prog);
14462     p = q;
14463     while (p >= prog && *p != '/' && *p != '\\') p--;
14464     p++;
14465     if(p == prog && *p == '"') p++;
14466     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14467     memcpy(buf, p, q - p);
14468     buf[q - p] = NULLCHAR;
14469     if (!local) {
14470         strcat(buf, "@");
14471         strcat(buf, host);
14472     }
14473 }
14474
14475 char *
14476 TimeControlTagValue()
14477 {
14478     char buf[MSG_SIZ];
14479     if (!appData.clockMode) {
14480       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14481     } else if (movesPerSession > 0) {
14482       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14483     } else if (timeIncrement == 0) {
14484       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14485     } else {
14486       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14487     }
14488     return StrSave(buf);
14489 }
14490
14491 void
14492 SetGameInfo()
14493 {
14494     /* This routine is used only for certain modes */
14495     VariantClass v = gameInfo.variant;
14496     ChessMove r = GameUnfinished;
14497     char *p = NULL;
14498
14499     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14500         r = gameInfo.result;
14501         p = gameInfo.resultDetails;
14502         gameInfo.resultDetails = NULL;
14503     }
14504     ClearGameInfo(&gameInfo);
14505     gameInfo.variant = v;
14506
14507     switch (gameMode) {
14508       case MachinePlaysWhite:
14509         gameInfo.event = StrSave( appData.pgnEventHeader );
14510         gameInfo.site = StrSave(HostName());
14511         gameInfo.date = PGNDate();
14512         gameInfo.round = StrSave("-");
14513         gameInfo.white = StrSave(first.tidy);
14514         gameInfo.black = StrSave(UserName());
14515         gameInfo.timeControl = TimeControlTagValue();
14516         break;
14517
14518       case MachinePlaysBlack:
14519         gameInfo.event = StrSave( appData.pgnEventHeader );
14520         gameInfo.site = StrSave(HostName());
14521         gameInfo.date = PGNDate();
14522         gameInfo.round = StrSave("-");
14523         gameInfo.white = StrSave(UserName());
14524         gameInfo.black = StrSave(first.tidy);
14525         gameInfo.timeControl = TimeControlTagValue();
14526         break;
14527
14528       case TwoMachinesPlay:
14529         gameInfo.event = StrSave( appData.pgnEventHeader );
14530         gameInfo.site = StrSave(HostName());
14531         gameInfo.date = PGNDate();
14532         if (roundNr > 0) {
14533             char buf[MSG_SIZ];
14534             snprintf(buf, MSG_SIZ, "%d", roundNr);
14535             gameInfo.round = StrSave(buf);
14536         } else {
14537             gameInfo.round = StrSave("-");
14538         }
14539         if (first.twoMachinesColor[0] == 'w') {
14540             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14541             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14542         } else {
14543             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14544             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14545         }
14546         gameInfo.timeControl = TimeControlTagValue();
14547         break;
14548
14549       case EditGame:
14550         gameInfo.event = StrSave("Edited game");
14551         gameInfo.site = StrSave(HostName());
14552         gameInfo.date = PGNDate();
14553         gameInfo.round = StrSave("-");
14554         gameInfo.white = StrSave("-");
14555         gameInfo.black = StrSave("-");
14556         gameInfo.result = r;
14557         gameInfo.resultDetails = p;
14558         break;
14559
14560       case EditPosition:
14561         gameInfo.event = StrSave("Edited position");
14562         gameInfo.site = StrSave(HostName());
14563         gameInfo.date = PGNDate();
14564         gameInfo.round = StrSave("-");
14565         gameInfo.white = StrSave("-");
14566         gameInfo.black = StrSave("-");
14567         break;
14568
14569       case IcsPlayingWhite:
14570       case IcsPlayingBlack:
14571       case IcsObserving:
14572       case IcsExamining:
14573         break;
14574
14575       case PlayFromGameFile:
14576         gameInfo.event = StrSave("Game from non-PGN file");
14577         gameInfo.site = StrSave(HostName());
14578         gameInfo.date = PGNDate();
14579         gameInfo.round = StrSave("-");
14580         gameInfo.white = StrSave("?");
14581         gameInfo.black = StrSave("?");
14582         break;
14583
14584       default:
14585         break;
14586     }
14587 }
14588
14589 void
14590 ReplaceComment(index, text)
14591      int index;
14592      char *text;
14593 {
14594     int len;
14595     char *p;
14596     float score;
14597
14598     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14599        pvInfoList[index-1].depth == len &&
14600        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14601        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14602     while (*text == '\n') text++;
14603     len = strlen(text);
14604     while (len > 0 && text[len - 1] == '\n') len--;
14605
14606     if (commentList[index] != NULL)
14607       free(commentList[index]);
14608
14609     if (len == 0) {
14610         commentList[index] = NULL;
14611         return;
14612     }
14613   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14614       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14615       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14616     commentList[index] = (char *) malloc(len + 2);
14617     strncpy(commentList[index], text, len);
14618     commentList[index][len] = '\n';
14619     commentList[index][len + 1] = NULLCHAR;
14620   } else {
14621     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14622     char *p;
14623     commentList[index] = (char *) malloc(len + 7);
14624     safeStrCpy(commentList[index], "{\n", 3);
14625     safeStrCpy(commentList[index]+2, text, len+1);
14626     commentList[index][len+2] = NULLCHAR;
14627     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14628     strcat(commentList[index], "\n}\n");
14629   }
14630 }
14631
14632 void
14633 CrushCRs(text)
14634      char *text;
14635 {
14636   char *p = text;
14637   char *q = text;
14638   char ch;
14639
14640   do {
14641     ch = *p++;
14642     if (ch == '\r') continue;
14643     *q++ = ch;
14644   } while (ch != '\0');
14645 }
14646
14647 void
14648 AppendComment(index, text, addBraces)
14649      int index;
14650      char *text;
14651      Boolean addBraces; // [HGM] braces: tells if we should add {}
14652 {
14653     int oldlen, len;
14654     char *old;
14655
14656 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14657     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14658
14659     CrushCRs(text);
14660     while (*text == '\n') text++;
14661     len = strlen(text);
14662     while (len > 0 && text[len - 1] == '\n') len--;
14663
14664     if (len == 0) return;
14665
14666     if (commentList[index] != NULL) {
14667       Boolean addClosingBrace = addBraces;
14668         old = commentList[index];
14669         oldlen = strlen(old);
14670         while(commentList[index][oldlen-1] ==  '\n')
14671           commentList[index][--oldlen] = NULLCHAR;
14672         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14673         safeStrCpy(commentList[index], old, oldlen + len + 6);
14674         free(old);
14675         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14676         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14677           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14678           while (*text == '\n') { text++; len--; }
14679           commentList[index][--oldlen] = NULLCHAR;
14680       }
14681         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14682         else          strcat(commentList[index], "\n");
14683         strcat(commentList[index], text);
14684         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14685         else          strcat(commentList[index], "\n");
14686     } else {
14687         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14688         if(addBraces)
14689           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14690         else commentList[index][0] = NULLCHAR;
14691         strcat(commentList[index], text);
14692         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14693         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14694     }
14695 }
14696
14697 static char * FindStr( char * text, char * sub_text )
14698 {
14699     char * result = strstr( text, sub_text );
14700
14701     if( result != NULL ) {
14702         result += strlen( sub_text );
14703     }
14704
14705     return result;
14706 }
14707
14708 /* [AS] Try to extract PV info from PGN comment */
14709 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14710 char *GetInfoFromComment( int index, char * text )
14711 {
14712     char * sep = text, *p;
14713
14714     if( text != NULL && index > 0 ) {
14715         int score = 0;
14716         int depth = 0;
14717         int time = -1, sec = 0, deci;
14718         char * s_eval = FindStr( text, "[%eval " );
14719         char * s_emt = FindStr( text, "[%emt " );
14720
14721         if( s_eval != NULL || s_emt != NULL ) {
14722             /* New style */
14723             char delim;
14724
14725             if( s_eval != NULL ) {
14726                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14727                     return text;
14728                 }
14729
14730                 if( delim != ']' ) {
14731                     return text;
14732                 }
14733             }
14734
14735             if( s_emt != NULL ) {
14736             }
14737                 return text;
14738         }
14739         else {
14740             /* We expect something like: [+|-]nnn.nn/dd */
14741             int score_lo = 0;
14742
14743             if(*text != '{') return text; // [HGM] braces: must be normal comment
14744
14745             sep = strchr( text, '/' );
14746             if( sep == NULL || sep < (text+4) ) {
14747                 return text;
14748             }
14749
14750             p = text;
14751             if(p[1] == '(') { // comment starts with PV
14752                p = strchr(p, ')'); // locate end of PV
14753                if(p == NULL || sep < p+5) return text;
14754                // at this point we have something like "{(.*) +0.23/6 ..."
14755                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14756                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14757                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14758             }
14759             time = -1; sec = -1; deci = -1;
14760             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14761                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14762                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14763                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14764                 return text;
14765             }
14766
14767             if( score_lo < 0 || score_lo >= 100 ) {
14768                 return text;
14769             }
14770
14771             if(sec >= 0) time = 600*time + 10*sec; else
14772             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14773
14774             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14775
14776             /* [HGM] PV time: now locate end of PV info */
14777             while( *++sep >= '0' && *sep <= '9'); // strip depth
14778             if(time >= 0)
14779             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14780             if(sec >= 0)
14781             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14782             if(deci >= 0)
14783             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14784             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14785         }
14786
14787         if( depth <= 0 ) {
14788             return text;
14789         }
14790
14791         if( time < 0 ) {
14792             time = -1;
14793         }
14794
14795         pvInfoList[index-1].depth = depth;
14796         pvInfoList[index-1].score = score;
14797         pvInfoList[index-1].time  = 10*time; // centi-sec
14798         if(*sep == '}') *sep = 0; else *--sep = '{';
14799         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14800     }
14801     return sep;
14802 }
14803
14804 void
14805 SendToProgram(message, cps)
14806      char *message;
14807      ChessProgramState *cps;
14808 {
14809     int count, outCount, error;
14810     char buf[MSG_SIZ];
14811
14812     if (cps->pr == NULL) return;
14813     Attention(cps);
14814
14815     if (appData.debugMode) {
14816         TimeMark now;
14817         GetTimeMark(&now);
14818         fprintf(debugFP, "%ld >%-6s: %s",
14819                 SubtractTimeMarks(&now, &programStartTime),
14820                 cps->which, message);
14821     }
14822
14823     count = strlen(message);
14824     outCount = OutputToProcess(cps->pr, message, count, &error);
14825     if (outCount < count && !exiting
14826                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14827       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14828       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14829         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14830             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14831                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14832                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14833                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14834             } else {
14835                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14836                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14837                 gameInfo.result = res;
14838             }
14839             gameInfo.resultDetails = StrSave(buf);
14840         }
14841         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14842         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14843     }
14844 }
14845
14846 void
14847 ReceiveFromProgram(isr, closure, message, count, error)
14848      InputSourceRef isr;
14849      VOIDSTAR closure;
14850      char *message;
14851      int count;
14852      int error;
14853 {
14854     char *end_str;
14855     char buf[MSG_SIZ];
14856     ChessProgramState *cps = (ChessProgramState *)closure;
14857
14858     if (isr != cps->isr) return; /* Killed intentionally */
14859     if (count <= 0) {
14860         if (count == 0) {
14861             RemoveInputSource(cps->isr);
14862             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14863             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14864                     _(cps->which), cps->program);
14865         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14866                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14867                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14868                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14869                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14870                 } else {
14871                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14872                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14873                     gameInfo.result = res;
14874                 }
14875                 gameInfo.resultDetails = StrSave(buf);
14876             }
14877             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14878             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14879         } else {
14880             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14881                     _(cps->which), cps->program);
14882             RemoveInputSource(cps->isr);
14883
14884             /* [AS] Program is misbehaving badly... kill it */
14885             if( count == -2 ) {
14886                 DestroyChildProcess( cps->pr, 9 );
14887                 cps->pr = NoProc;
14888             }
14889
14890             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14891         }
14892         return;
14893     }
14894
14895     if ((end_str = strchr(message, '\r')) != NULL)
14896       *end_str = NULLCHAR;
14897     if ((end_str = strchr(message, '\n')) != NULL)
14898       *end_str = NULLCHAR;
14899
14900     if (appData.debugMode) {
14901         TimeMark now; int print = 1;
14902         char *quote = ""; char c; int i;
14903
14904         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14905                 char start = message[0];
14906                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14907                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14908                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14909                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14910                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14911                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14912                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14913                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14914                    sscanf(message, "hint: %c", &c)!=1 && 
14915                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14916                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14917                     print = (appData.engineComments >= 2);
14918                 }
14919                 message[0] = start; // restore original message
14920         }
14921         if(print) {
14922                 GetTimeMark(&now);
14923                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14924                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14925                         quote,
14926                         message);
14927         }
14928     }
14929
14930     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14931     if (appData.icsEngineAnalyze) {
14932         if (strstr(message, "whisper") != NULL ||
14933              strstr(message, "kibitz") != NULL ||
14934             strstr(message, "tellics") != NULL) return;
14935     }
14936
14937     HandleMachineMove(message, cps);
14938 }
14939
14940
14941 void
14942 SendTimeControl(cps, mps, tc, inc, sd, st)
14943      ChessProgramState *cps;
14944      int mps, inc, sd, st;
14945      long tc;
14946 {
14947     char buf[MSG_SIZ];
14948     int seconds;
14949
14950     if( timeControl_2 > 0 ) {
14951         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14952             tc = timeControl_2;
14953         }
14954     }
14955     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14956     inc /= cps->timeOdds;
14957     st  /= cps->timeOdds;
14958
14959     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14960
14961     if (st > 0) {
14962       /* Set exact time per move, normally using st command */
14963       if (cps->stKludge) {
14964         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14965         seconds = st % 60;
14966         if (seconds == 0) {
14967           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14968         } else {
14969           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14970         }
14971       } else {
14972         snprintf(buf, MSG_SIZ, "st %d\n", st);
14973       }
14974     } else {
14975       /* Set conventional or incremental time control, using level command */
14976       if (seconds == 0) {
14977         /* Note old gnuchess bug -- minutes:seconds used to not work.
14978            Fixed in later versions, but still avoid :seconds
14979            when seconds is 0. */
14980         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14981       } else {
14982         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14983                  seconds, inc/1000.);
14984       }
14985     }
14986     SendToProgram(buf, cps);
14987
14988     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14989     /* Orthogonally, limit search to given depth */
14990     if (sd > 0) {
14991       if (cps->sdKludge) {
14992         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14993       } else {
14994         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14995       }
14996       SendToProgram(buf, cps);
14997     }
14998
14999     if(cps->nps >= 0) { /* [HGM] nps */
15000         if(cps->supportsNPS == FALSE)
15001           cps->nps = -1; // don't use if engine explicitly says not supported!
15002         else {
15003           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15004           SendToProgram(buf, cps);
15005         }
15006     }
15007 }
15008
15009 ChessProgramState *WhitePlayer()
15010 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15011 {
15012     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15013        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15014         return &second;
15015     return &first;
15016 }
15017
15018 void
15019 SendTimeRemaining(cps, machineWhite)
15020      ChessProgramState *cps;
15021      int /*boolean*/ machineWhite;
15022 {
15023     char message[MSG_SIZ];
15024     long time, otime;
15025
15026     /* Note: this routine must be called when the clocks are stopped
15027        or when they have *just* been set or switched; otherwise
15028        it will be off by the time since the current tick started.
15029     */
15030     if (machineWhite) {
15031         time = whiteTimeRemaining / 10;
15032         otime = blackTimeRemaining / 10;
15033     } else {
15034         time = blackTimeRemaining / 10;
15035         otime = whiteTimeRemaining / 10;
15036     }
15037     /* [HGM] translate opponent's time by time-odds factor */
15038     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15039     if (appData.debugMode) {
15040         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15041     }
15042
15043     if (time <= 0) time = 1;
15044     if (otime <= 0) otime = 1;
15045
15046     snprintf(message, MSG_SIZ, "time %ld\n", time);
15047     SendToProgram(message, cps);
15048
15049     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15050     SendToProgram(message, cps);
15051 }
15052
15053 int
15054 BoolFeature(p, name, loc, cps)
15055      char **p;
15056      char *name;
15057      int *loc;
15058      ChessProgramState *cps;
15059 {
15060   char buf[MSG_SIZ];
15061   int len = strlen(name);
15062   int val;
15063
15064   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15065     (*p) += len + 1;
15066     sscanf(*p, "%d", &val);
15067     *loc = (val != 0);
15068     while (**p && **p != ' ')
15069       (*p)++;
15070     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15071     SendToProgram(buf, cps);
15072     return TRUE;
15073   }
15074   return FALSE;
15075 }
15076
15077 int
15078 IntFeature(p, name, loc, cps)
15079      char **p;
15080      char *name;
15081      int *loc;
15082      ChessProgramState *cps;
15083 {
15084   char buf[MSG_SIZ];
15085   int len = strlen(name);
15086   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15087     (*p) += len + 1;
15088     sscanf(*p, "%d", loc);
15089     while (**p && **p != ' ') (*p)++;
15090     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15091     SendToProgram(buf, cps);
15092     return TRUE;
15093   }
15094   return FALSE;
15095 }
15096
15097 int
15098 StringFeature(p, name, loc, cps)
15099      char **p;
15100      char *name;
15101      char loc[];
15102      ChessProgramState *cps;
15103 {
15104   char buf[MSG_SIZ];
15105   int len = strlen(name);
15106   if (strncmp((*p), name, len) == 0
15107       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15108     (*p) += len + 2;
15109     sscanf(*p, "%[^\"]", loc);
15110     while (**p && **p != '\"') (*p)++;
15111     if (**p == '\"') (*p)++;
15112     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15113     SendToProgram(buf, cps);
15114     return TRUE;
15115   }
15116   return FALSE;
15117 }
15118
15119 int
15120 ParseOption(Option *opt, ChessProgramState *cps)
15121 // [HGM] options: process the string that defines an engine option, and determine
15122 // name, type, default value, and allowed value range
15123 {
15124         char *p, *q, buf[MSG_SIZ];
15125         int n, min = (-1)<<31, max = 1<<31, def;
15126
15127         if(p = strstr(opt->name, " -spin ")) {
15128             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15129             if(max < min) max = min; // enforce consistency
15130             if(def < min) def = min;
15131             if(def > max) def = max;
15132             opt->value = def;
15133             opt->min = min;
15134             opt->max = max;
15135             opt->type = Spin;
15136         } else if((p = strstr(opt->name, " -slider "))) {
15137             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15138             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15139             if(max < min) max = min; // enforce consistency
15140             if(def < min) def = min;
15141             if(def > max) def = max;
15142             opt->value = def;
15143             opt->min = min;
15144             opt->max = max;
15145             opt->type = Spin; // Slider;
15146         } else if((p = strstr(opt->name, " -string "))) {
15147             opt->textValue = p+9;
15148             opt->type = TextBox;
15149         } else if((p = strstr(opt->name, " -file "))) {
15150             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15151             opt->textValue = p+7;
15152             opt->type = FileName; // FileName;
15153         } else if((p = strstr(opt->name, " -path "))) {
15154             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15155             opt->textValue = p+7;
15156             opt->type = PathName; // PathName;
15157         } else if(p = strstr(opt->name, " -check ")) {
15158             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15159             opt->value = (def != 0);
15160             opt->type = CheckBox;
15161         } else if(p = strstr(opt->name, " -combo ")) {
15162             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15163             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15164             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15165             opt->value = n = 0;
15166             while(q = StrStr(q, " /// ")) {
15167                 n++; *q = 0;    // count choices, and null-terminate each of them
15168                 q += 5;
15169                 if(*q == '*') { // remember default, which is marked with * prefix
15170                     q++;
15171                     opt->value = n;
15172                 }
15173                 cps->comboList[cps->comboCnt++] = q;
15174             }
15175             cps->comboList[cps->comboCnt++] = NULL;
15176             opt->max = n + 1;
15177             opt->type = ComboBox;
15178         } else if(p = strstr(opt->name, " -button")) {
15179             opt->type = Button;
15180         } else if(p = strstr(opt->name, " -save")) {
15181             opt->type = SaveButton;
15182         } else return FALSE;
15183         *p = 0; // terminate option name
15184         // now look if the command-line options define a setting for this engine option.
15185         if(cps->optionSettings && cps->optionSettings[0])
15186             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15187         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15188           snprintf(buf, MSG_SIZ, "option %s", p);
15189                 if(p = strstr(buf, ",")) *p = 0;
15190                 if(q = strchr(buf, '=')) switch(opt->type) {
15191                     case ComboBox:
15192                         for(n=0; n<opt->max; n++)
15193                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15194                         break;
15195                     case TextBox:
15196                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15197                         break;
15198                     case Spin:
15199                     case CheckBox:
15200                         opt->value = atoi(q+1);
15201                     default:
15202                         break;
15203                 }
15204                 strcat(buf, "\n");
15205                 SendToProgram(buf, cps);
15206         }
15207         return TRUE;
15208 }
15209
15210 void
15211 FeatureDone(cps, val)
15212      ChessProgramState* cps;
15213      int val;
15214 {
15215   DelayedEventCallback cb = GetDelayedEvent();
15216   if ((cb == InitBackEnd3 && cps == &first) ||
15217       (cb == SettingsMenuIfReady && cps == &second) ||
15218       (cb == LoadEngine) ||
15219       (cb == TwoMachinesEventIfReady)) {
15220     CancelDelayedEvent();
15221     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15222   }
15223   cps->initDone = val;
15224 }
15225
15226 /* Parse feature command from engine */
15227 void
15228 ParseFeatures(args, cps)
15229      char* args;
15230      ChessProgramState *cps;
15231 {
15232   char *p = args;
15233   char *q;
15234   int val;
15235   char buf[MSG_SIZ];
15236
15237   for (;;) {
15238     while (*p == ' ') p++;
15239     if (*p == NULLCHAR) return;
15240
15241     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15242     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15243     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15244     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15245     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15246     if (BoolFeature(&p, "reuse", &val, cps)) {
15247       /* Engine can disable reuse, but can't enable it if user said no */
15248       if (!val) cps->reuse = FALSE;
15249       continue;
15250     }
15251     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15252     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15253       if (gameMode == TwoMachinesPlay) {
15254         DisplayTwoMachinesTitle();
15255       } else {
15256         DisplayTitle("");
15257       }
15258       continue;
15259     }
15260     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15261     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15262     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15263     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15264     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15265     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15266     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15267     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15268     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15269     if (IntFeature(&p, "done", &val, cps)) {
15270       FeatureDone(cps, val);
15271       continue;
15272     }
15273     /* Added by Tord: */
15274     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15275     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15276     /* End of additions by Tord */
15277
15278     /* [HGM] added features: */
15279     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15280     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15281     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15282     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15283     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15284     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15285     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15286         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15287           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15288             SendToProgram(buf, cps);
15289             continue;
15290         }
15291         if(cps->nrOptions >= MAX_OPTIONS) {
15292             cps->nrOptions--;
15293             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15294             DisplayError(buf, 0);
15295         }
15296         continue;
15297     }
15298     /* End of additions by HGM */
15299
15300     /* unknown feature: complain and skip */
15301     q = p;
15302     while (*q && *q != '=') q++;
15303     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15304     SendToProgram(buf, cps);
15305     p = q;
15306     if (*p == '=') {
15307       p++;
15308       if (*p == '\"') {
15309         p++;
15310         while (*p && *p != '\"') p++;
15311         if (*p == '\"') p++;
15312       } else {
15313         while (*p && *p != ' ') p++;
15314       }
15315     }
15316   }
15317
15318 }
15319
15320 void
15321 PeriodicUpdatesEvent(newState)
15322      int newState;
15323 {
15324     if (newState == appData.periodicUpdates)
15325       return;
15326
15327     appData.periodicUpdates=newState;
15328
15329     /* Display type changes, so update it now */
15330 //    DisplayAnalysis();
15331
15332     /* Get the ball rolling again... */
15333     if (newState) {
15334         AnalysisPeriodicEvent(1);
15335         StartAnalysisClock();
15336     }
15337 }
15338
15339 void
15340 PonderNextMoveEvent(newState)
15341      int newState;
15342 {
15343     if (newState == appData.ponderNextMove) return;
15344     if (gameMode == EditPosition) EditPositionDone(TRUE);
15345     if (newState) {
15346         SendToProgram("hard\n", &first);
15347         if (gameMode == TwoMachinesPlay) {
15348             SendToProgram("hard\n", &second);
15349         }
15350     } else {
15351         SendToProgram("easy\n", &first);
15352         thinkOutput[0] = NULLCHAR;
15353         if (gameMode == TwoMachinesPlay) {
15354             SendToProgram("easy\n", &second);
15355         }
15356     }
15357     appData.ponderNextMove = newState;
15358 }
15359
15360 void
15361 NewSettingEvent(option, feature, command, value)
15362      char *command;
15363      int option, value, *feature;
15364 {
15365     char buf[MSG_SIZ];
15366
15367     if (gameMode == EditPosition) EditPositionDone(TRUE);
15368     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15369     if(feature == NULL || *feature) SendToProgram(buf, &first);
15370     if (gameMode == TwoMachinesPlay) {
15371         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15372     }
15373 }
15374
15375 void
15376 ShowThinkingEvent()
15377 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15378 {
15379     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15380     int newState = appData.showThinking
15381         // [HGM] thinking: other features now need thinking output as well
15382         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15383
15384     if (oldState == newState) return;
15385     oldState = newState;
15386     if (gameMode == EditPosition) EditPositionDone(TRUE);
15387     if (oldState) {
15388         SendToProgram("post\n", &first);
15389         if (gameMode == TwoMachinesPlay) {
15390             SendToProgram("post\n", &second);
15391         }
15392     } else {
15393         SendToProgram("nopost\n", &first);
15394         thinkOutput[0] = NULLCHAR;
15395         if (gameMode == TwoMachinesPlay) {
15396             SendToProgram("nopost\n", &second);
15397         }
15398     }
15399 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15400 }
15401
15402 void
15403 AskQuestionEvent(title, question, replyPrefix, which)
15404      char *title; char *question; char *replyPrefix; char *which;
15405 {
15406   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15407   if (pr == NoProc) return;
15408   AskQuestion(title, question, replyPrefix, pr);
15409 }
15410
15411 void
15412 TypeInEvent(char firstChar)
15413 {
15414     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15415         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15416         gameMode == AnalyzeMode || gameMode == EditGame || 
15417         gameMode == EditPosition || gameMode == IcsExamining ||
15418         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15419         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15420                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15421                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15422         gameMode == Training) PopUpMoveDialog(firstChar);
15423 }
15424
15425 void
15426 TypeInDoneEvent(char *move)
15427 {
15428         Board board;
15429         int n, fromX, fromY, toX, toY;
15430         char promoChar;
15431         ChessMove moveType;
15432
15433         // [HGM] FENedit
15434         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15435                 EditPositionPasteFEN(move);
15436                 return;
15437         }
15438         // [HGM] movenum: allow move number to be typed in any mode
15439         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15440           ToNrEvent(2*n-1);
15441           return;
15442         }
15443
15444       if (gameMode != EditGame && currentMove != forwardMostMove && 
15445         gameMode != Training) {
15446         DisplayMoveError(_("Displayed move is not current"));
15447       } else {
15448         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15449           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15450         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15451         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15452           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15453           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15454         } else {
15455           DisplayMoveError(_("Could not parse move"));
15456         }
15457       }
15458 }
15459
15460 void
15461 DisplayMove(moveNumber)
15462      int moveNumber;
15463 {
15464     char message[MSG_SIZ];
15465     char res[MSG_SIZ];
15466     char cpThinkOutput[MSG_SIZ];
15467
15468     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15469
15470     if (moveNumber == forwardMostMove - 1 ||
15471         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15472
15473         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15474
15475         if (strchr(cpThinkOutput, '\n')) {
15476             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15477         }
15478     } else {
15479         *cpThinkOutput = NULLCHAR;
15480     }
15481
15482     /* [AS] Hide thinking from human user */
15483     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15484         *cpThinkOutput = NULLCHAR;
15485         if( thinkOutput[0] != NULLCHAR ) {
15486             int i;
15487
15488             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15489                 cpThinkOutput[i] = '.';
15490             }
15491             cpThinkOutput[i] = NULLCHAR;
15492             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15493         }
15494     }
15495
15496     if (moveNumber == forwardMostMove - 1 &&
15497         gameInfo.resultDetails != NULL) {
15498         if (gameInfo.resultDetails[0] == NULLCHAR) {
15499           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15500         } else {
15501           snprintf(res, MSG_SIZ, " {%s} %s",
15502                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15503         }
15504     } else {
15505         res[0] = NULLCHAR;
15506     }
15507
15508     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15509         DisplayMessage(res, cpThinkOutput);
15510     } else {
15511       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15512                 WhiteOnMove(moveNumber) ? " " : ".. ",
15513                 parseList[moveNumber], res);
15514         DisplayMessage(message, cpThinkOutput);
15515     }
15516 }
15517
15518 void
15519 DisplayComment(moveNumber, text)
15520      int moveNumber;
15521      char *text;
15522 {
15523     char title[MSG_SIZ];
15524
15525     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15526       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15527     } else {
15528       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15529               WhiteOnMove(moveNumber) ? " " : ".. ",
15530               parseList[moveNumber]);
15531     }
15532     if (text != NULL && (appData.autoDisplayComment || commentUp))
15533         CommentPopUp(title, text);
15534 }
15535
15536 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15537  * might be busy thinking or pondering.  It can be omitted if your
15538  * gnuchess is configured to stop thinking immediately on any user
15539  * input.  However, that gnuchess feature depends on the FIONREAD
15540  * ioctl, which does not work properly on some flavors of Unix.
15541  */
15542 void
15543 Attention(cps)
15544      ChessProgramState *cps;
15545 {
15546 #if ATTENTION
15547     if (!cps->useSigint) return;
15548     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15549     switch (gameMode) {
15550       case MachinePlaysWhite:
15551       case MachinePlaysBlack:
15552       case TwoMachinesPlay:
15553       case IcsPlayingWhite:
15554       case IcsPlayingBlack:
15555       case AnalyzeMode:
15556       case AnalyzeFile:
15557         /* Skip if we know it isn't thinking */
15558         if (!cps->maybeThinking) return;
15559         if (appData.debugMode)
15560           fprintf(debugFP, "Interrupting %s\n", cps->which);
15561         InterruptChildProcess(cps->pr);
15562         cps->maybeThinking = FALSE;
15563         break;
15564       default:
15565         break;
15566     }
15567 #endif /*ATTENTION*/
15568 }
15569
15570 int
15571 CheckFlags()
15572 {
15573     if (whiteTimeRemaining <= 0) {
15574         if (!whiteFlag) {
15575             whiteFlag = TRUE;
15576             if (appData.icsActive) {
15577                 if (appData.autoCallFlag &&
15578                     gameMode == IcsPlayingBlack && !blackFlag) {
15579                   SendToICS(ics_prefix);
15580                   SendToICS("flag\n");
15581                 }
15582             } else {
15583                 if (blackFlag) {
15584                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15585                 } else {
15586                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15587                     if (appData.autoCallFlag) {
15588                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15589                         return TRUE;
15590                     }
15591                 }
15592             }
15593         }
15594     }
15595     if (blackTimeRemaining <= 0) {
15596         if (!blackFlag) {
15597             blackFlag = TRUE;
15598             if (appData.icsActive) {
15599                 if (appData.autoCallFlag &&
15600                     gameMode == IcsPlayingWhite && !whiteFlag) {
15601                   SendToICS(ics_prefix);
15602                   SendToICS("flag\n");
15603                 }
15604             } else {
15605                 if (whiteFlag) {
15606                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15607                 } else {
15608                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15609                     if (appData.autoCallFlag) {
15610                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15611                         return TRUE;
15612                     }
15613                 }
15614             }
15615         }
15616     }
15617     return FALSE;
15618 }
15619
15620 void
15621 CheckTimeControl()
15622 {
15623     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15624         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15625
15626     /*
15627      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15628      */
15629     if ( !WhiteOnMove(forwardMostMove) ) {
15630         /* White made time control */
15631         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15632         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15633         /* [HGM] time odds: correct new time quota for time odds! */
15634                                             / WhitePlayer()->timeOdds;
15635         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15636     } else {
15637         lastBlack -= blackTimeRemaining;
15638         /* Black made time control */
15639         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15640                                             / WhitePlayer()->other->timeOdds;
15641         lastWhite = whiteTimeRemaining;
15642     }
15643 }
15644
15645 void
15646 DisplayBothClocks()
15647 {
15648     int wom = gameMode == EditPosition ?
15649       !blackPlaysFirst : WhiteOnMove(currentMove);
15650     DisplayWhiteClock(whiteTimeRemaining, wom);
15651     DisplayBlackClock(blackTimeRemaining, !wom);
15652 }
15653
15654
15655 /* Timekeeping seems to be a portability nightmare.  I think everyone
15656    has ftime(), but I'm really not sure, so I'm including some ifdefs
15657    to use other calls if you don't.  Clocks will be less accurate if
15658    you have neither ftime nor gettimeofday.
15659 */
15660
15661 /* VS 2008 requires the #include outside of the function */
15662 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15663 #include <sys/timeb.h>
15664 #endif
15665
15666 /* Get the current time as a TimeMark */
15667 void
15668 GetTimeMark(tm)
15669      TimeMark *tm;
15670 {
15671 #if HAVE_GETTIMEOFDAY
15672
15673     struct timeval timeVal;
15674     struct timezone timeZone;
15675
15676     gettimeofday(&timeVal, &timeZone);
15677     tm->sec = (long) timeVal.tv_sec;
15678     tm->ms = (int) (timeVal.tv_usec / 1000L);
15679
15680 #else /*!HAVE_GETTIMEOFDAY*/
15681 #if HAVE_FTIME
15682
15683 // include <sys/timeb.h> / moved to just above start of function
15684     struct timeb timeB;
15685
15686     ftime(&timeB);
15687     tm->sec = (long) timeB.time;
15688     tm->ms = (int) timeB.millitm;
15689
15690 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15691     tm->sec = (long) time(NULL);
15692     tm->ms = 0;
15693 #endif
15694 #endif
15695 }
15696
15697 /* Return the difference in milliseconds between two
15698    time marks.  We assume the difference will fit in a long!
15699 */
15700 long
15701 SubtractTimeMarks(tm2, tm1)
15702      TimeMark *tm2, *tm1;
15703 {
15704     return 1000L*(tm2->sec - tm1->sec) +
15705            (long) (tm2->ms - tm1->ms);
15706 }
15707
15708
15709 /*
15710  * Code to manage the game clocks.
15711  *
15712  * In tournament play, black starts the clock and then white makes a move.
15713  * We give the human user a slight advantage if he is playing white---the
15714  * clocks don't run until he makes his first move, so it takes zero time.
15715  * Also, we don't account for network lag, so we could get out of sync
15716  * with GNU Chess's clock -- but then, referees are always right.
15717  */
15718
15719 static TimeMark tickStartTM;
15720 static long intendedTickLength;
15721
15722 long
15723 NextTickLength(timeRemaining)
15724      long timeRemaining;
15725 {
15726     long nominalTickLength, nextTickLength;
15727
15728     if (timeRemaining > 0L && timeRemaining <= 10000L)
15729       nominalTickLength = 100L;
15730     else
15731       nominalTickLength = 1000L;
15732     nextTickLength = timeRemaining % nominalTickLength;
15733     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15734
15735     return nextTickLength;
15736 }
15737
15738 /* Adjust clock one minute up or down */
15739 void
15740 AdjustClock(Boolean which, int dir)
15741 {
15742     if(which) blackTimeRemaining += 60000*dir;
15743     else      whiteTimeRemaining += 60000*dir;
15744     DisplayBothClocks();
15745 }
15746
15747 /* Stop clocks and reset to a fresh time control */
15748 void
15749 ResetClocks()
15750 {
15751     (void) StopClockTimer();
15752     if (appData.icsActive) {
15753         whiteTimeRemaining = blackTimeRemaining = 0;
15754     } else if (searchTime) {
15755         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15756         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15757     } else { /* [HGM] correct new time quote for time odds */
15758         whiteTC = blackTC = fullTimeControlString;
15759         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15760         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15761     }
15762     if (whiteFlag || blackFlag) {
15763         DisplayTitle("");
15764         whiteFlag = blackFlag = FALSE;
15765     }
15766     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15767     DisplayBothClocks();
15768 }
15769
15770 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15771
15772 /* Decrement running clock by amount of time that has passed */
15773 void
15774 DecrementClocks()
15775 {
15776     long timeRemaining;
15777     long lastTickLength, fudge;
15778     TimeMark now;
15779
15780     if (!appData.clockMode) return;
15781     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15782
15783     GetTimeMark(&now);
15784
15785     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15786
15787     /* Fudge if we woke up a little too soon */
15788     fudge = intendedTickLength - lastTickLength;
15789     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15790
15791     if (WhiteOnMove(forwardMostMove)) {
15792         if(whiteNPS >= 0) lastTickLength = 0;
15793         timeRemaining = whiteTimeRemaining -= lastTickLength;
15794         if(timeRemaining < 0 && !appData.icsActive) {
15795             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15796             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15797                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15798                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15799             }
15800         }
15801         DisplayWhiteClock(whiteTimeRemaining - fudge,
15802                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15803     } else {
15804         if(blackNPS >= 0) lastTickLength = 0;
15805         timeRemaining = blackTimeRemaining -= lastTickLength;
15806         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15807             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15808             if(suddenDeath) {
15809                 blackStartMove = forwardMostMove;
15810                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15811             }
15812         }
15813         DisplayBlackClock(blackTimeRemaining - fudge,
15814                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15815     }
15816     if (CheckFlags()) return;
15817
15818     tickStartTM = now;
15819     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15820     StartClockTimer(intendedTickLength);
15821
15822     /* if the time remaining has fallen below the alarm threshold, sound the
15823      * alarm. if the alarm has sounded and (due to a takeback or time control
15824      * with increment) the time remaining has increased to a level above the
15825      * threshold, reset the alarm so it can sound again.
15826      */
15827
15828     if (appData.icsActive && appData.icsAlarm) {
15829
15830         /* make sure we are dealing with the user's clock */
15831         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15832                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15833            )) return;
15834
15835         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15836             alarmSounded = FALSE;
15837         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15838             PlayAlarmSound();
15839             alarmSounded = TRUE;
15840         }
15841     }
15842 }
15843
15844
15845 /* A player has just moved, so stop the previously running
15846    clock and (if in clock mode) start the other one.
15847    We redisplay both clocks in case we're in ICS mode, because
15848    ICS gives us an update to both clocks after every move.
15849    Note that this routine is called *after* forwardMostMove
15850    is updated, so the last fractional tick must be subtracted
15851    from the color that is *not* on move now.
15852 */
15853 void
15854 SwitchClocks(int newMoveNr)
15855 {
15856     long lastTickLength;
15857     TimeMark now;
15858     int flagged = FALSE;
15859
15860     GetTimeMark(&now);
15861
15862     if (StopClockTimer() && appData.clockMode) {
15863         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15864         if (!WhiteOnMove(forwardMostMove)) {
15865             if(blackNPS >= 0) lastTickLength = 0;
15866             blackTimeRemaining -= lastTickLength;
15867            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15868 //         if(pvInfoList[forwardMostMove].time == -1)
15869                  pvInfoList[forwardMostMove].time =               // use GUI time
15870                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15871         } else {
15872            if(whiteNPS >= 0) lastTickLength = 0;
15873            whiteTimeRemaining -= lastTickLength;
15874            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15875 //         if(pvInfoList[forwardMostMove].time == -1)
15876                  pvInfoList[forwardMostMove].time =
15877                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15878         }
15879         flagged = CheckFlags();
15880     }
15881     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15882     CheckTimeControl();
15883
15884     if (flagged || !appData.clockMode) return;
15885
15886     switch (gameMode) {
15887       case MachinePlaysBlack:
15888       case MachinePlaysWhite:
15889       case BeginningOfGame:
15890         if (pausing) return;
15891         break;
15892
15893       case EditGame:
15894       case PlayFromGameFile:
15895       case IcsExamining:
15896         return;
15897
15898       default:
15899         break;
15900     }
15901
15902     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15903         if(WhiteOnMove(forwardMostMove))
15904              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15905         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15906     }
15907
15908     tickStartTM = now;
15909     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15910       whiteTimeRemaining : blackTimeRemaining);
15911     StartClockTimer(intendedTickLength);
15912 }
15913
15914
15915 /* Stop both clocks */
15916 void
15917 StopClocks()
15918 {
15919     long lastTickLength;
15920     TimeMark now;
15921
15922     if (!StopClockTimer()) return;
15923     if (!appData.clockMode) return;
15924
15925     GetTimeMark(&now);
15926
15927     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15928     if (WhiteOnMove(forwardMostMove)) {
15929         if(whiteNPS >= 0) lastTickLength = 0;
15930         whiteTimeRemaining -= lastTickLength;
15931         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15932     } else {
15933         if(blackNPS >= 0) lastTickLength = 0;
15934         blackTimeRemaining -= lastTickLength;
15935         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15936     }
15937     CheckFlags();
15938 }
15939
15940 /* Start clock of player on move.  Time may have been reset, so
15941    if clock is already running, stop and restart it. */
15942 void
15943 StartClocks()
15944 {
15945     (void) StopClockTimer(); /* in case it was running already */
15946     DisplayBothClocks();
15947     if (CheckFlags()) return;
15948
15949     if (!appData.clockMode) return;
15950     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15951
15952     GetTimeMark(&tickStartTM);
15953     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15954       whiteTimeRemaining : blackTimeRemaining);
15955
15956    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15957     whiteNPS = blackNPS = -1;
15958     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15959        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15960         whiteNPS = first.nps;
15961     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15962        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15963         blackNPS = first.nps;
15964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15965         whiteNPS = second.nps;
15966     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15967         blackNPS = second.nps;
15968     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15969
15970     StartClockTimer(intendedTickLength);
15971 }
15972
15973 char *
15974 TimeString(ms)
15975      long ms;
15976 {
15977     long second, minute, hour, day;
15978     char *sign = "";
15979     static char buf[32];
15980
15981     if (ms > 0 && ms <= 9900) {
15982       /* convert milliseconds to tenths, rounding up */
15983       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15984
15985       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15986       return buf;
15987     }
15988
15989     /* convert milliseconds to seconds, rounding up */
15990     /* use floating point to avoid strangeness of integer division
15991        with negative dividends on many machines */
15992     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15993
15994     if (second < 0) {
15995         sign = "-";
15996         second = -second;
15997     }
15998
15999     day = second / (60 * 60 * 24);
16000     second = second % (60 * 60 * 24);
16001     hour = second / (60 * 60);
16002     second = second % (60 * 60);
16003     minute = second / 60;
16004     second = second % 60;
16005
16006     if (day > 0)
16007       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16008               sign, day, hour, minute, second);
16009     else if (hour > 0)
16010       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16011     else
16012       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16013
16014     return buf;
16015 }
16016
16017
16018 /*
16019  * This is necessary because some C libraries aren't ANSI C compliant yet.
16020  */
16021 char *
16022 StrStr(string, match)
16023      char *string, *match;
16024 {
16025     int i, length;
16026
16027     length = strlen(match);
16028
16029     for (i = strlen(string) - length; i >= 0; i--, string++)
16030       if (!strncmp(match, string, length))
16031         return string;
16032
16033     return NULL;
16034 }
16035
16036 char *
16037 StrCaseStr(string, match)
16038      char *string, *match;
16039 {
16040     int i, j, length;
16041
16042     length = strlen(match);
16043
16044     for (i = strlen(string) - length; i >= 0; i--, string++) {
16045         for (j = 0; j < length; j++) {
16046             if (ToLower(match[j]) != ToLower(string[j]))
16047               break;
16048         }
16049         if (j == length) return string;
16050     }
16051
16052     return NULL;
16053 }
16054
16055 #ifndef _amigados
16056 int
16057 StrCaseCmp(s1, s2)
16058      char *s1, *s2;
16059 {
16060     char c1, c2;
16061
16062     for (;;) {
16063         c1 = ToLower(*s1++);
16064         c2 = ToLower(*s2++);
16065         if (c1 > c2) return 1;
16066         if (c1 < c2) return -1;
16067         if (c1 == NULLCHAR) return 0;
16068     }
16069 }
16070
16071
16072 int
16073 ToLower(c)
16074      int c;
16075 {
16076     return isupper(c) ? tolower(c) : c;
16077 }
16078
16079
16080 int
16081 ToUpper(c)
16082      int c;
16083 {
16084     return islower(c) ? toupper(c) : c;
16085 }
16086 #endif /* !_amigados    */
16087
16088 char *
16089 StrSave(s)
16090      char *s;
16091 {
16092   char *ret;
16093
16094   if ((ret = (char *) malloc(strlen(s) + 1)))
16095     {
16096       safeStrCpy(ret, s, strlen(s)+1);
16097     }
16098   return ret;
16099 }
16100
16101 char *
16102 StrSavePtr(s, savePtr)
16103      char *s, **savePtr;
16104 {
16105     if (*savePtr) {
16106         free(*savePtr);
16107     }
16108     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16109       safeStrCpy(*savePtr, s, strlen(s)+1);
16110     }
16111     return(*savePtr);
16112 }
16113
16114 char *
16115 PGNDate()
16116 {
16117     time_t clock;
16118     struct tm *tm;
16119     char buf[MSG_SIZ];
16120
16121     clock = time((time_t *)NULL);
16122     tm = localtime(&clock);
16123     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16124             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16125     return StrSave(buf);
16126 }
16127
16128
16129 char *
16130 PositionToFEN(move, overrideCastling)
16131      int move;
16132      char *overrideCastling;
16133 {
16134     int i, j, fromX, fromY, toX, toY;
16135     int whiteToPlay;
16136     char buf[MSG_SIZ];
16137     char *p, *q;
16138     int emptycount;
16139     ChessSquare piece;
16140
16141     whiteToPlay = (gameMode == EditPosition) ?
16142       !blackPlaysFirst : (move % 2 == 0);
16143     p = buf;
16144
16145     /* Piece placement data */
16146     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16147         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16148         emptycount = 0;
16149         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16150             if (boards[move][i][j] == EmptySquare) {
16151                 emptycount++;
16152             } else { ChessSquare piece = boards[move][i][j];
16153                 if (emptycount > 0) {
16154                     if(emptycount<10) /* [HGM] can be >= 10 */
16155                         *p++ = '0' + emptycount;
16156                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16157                     emptycount = 0;
16158                 }
16159                 if(PieceToChar(piece) == '+') {
16160                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16161                     *p++ = '+';
16162                     piece = (ChessSquare)(DEMOTED piece);
16163                 }
16164                 *p++ = PieceToChar(piece);
16165                 if(p[-1] == '~') {
16166                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16167                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16168                     *p++ = '~';
16169                 }
16170             }
16171         }
16172         if (emptycount > 0) {
16173             if(emptycount<10) /* [HGM] can be >= 10 */
16174                 *p++ = '0' + emptycount;
16175             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16176             emptycount = 0;
16177         }
16178         *p++ = '/';
16179     }
16180     *(p - 1) = ' ';
16181
16182     /* [HGM] print Crazyhouse or Shogi holdings */
16183     if( gameInfo.holdingsWidth ) {
16184         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16185         q = p;
16186         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16187             piece = boards[move][i][BOARD_WIDTH-1];
16188             if( piece != EmptySquare )
16189               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16190                   *p++ = PieceToChar(piece);
16191         }
16192         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16193             piece = boards[move][BOARD_HEIGHT-i-1][0];
16194             if( piece != EmptySquare )
16195               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16196                   *p++ = PieceToChar(piece);
16197         }
16198
16199         if( q == p ) *p++ = '-';
16200         *p++ = ']';
16201         *p++ = ' ';
16202     }
16203
16204     /* Active color */
16205     *p++ = whiteToPlay ? 'w' : 'b';
16206     *p++ = ' ';
16207
16208   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16209     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16210   } else {
16211   if(nrCastlingRights) {
16212      q = p;
16213      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16214        /* [HGM] write directly from rights */
16215            if(boards[move][CASTLING][2] != NoRights &&
16216               boards[move][CASTLING][0] != NoRights   )
16217                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16218            if(boards[move][CASTLING][2] != NoRights &&
16219               boards[move][CASTLING][1] != NoRights   )
16220                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16221            if(boards[move][CASTLING][5] != NoRights &&
16222               boards[move][CASTLING][3] != NoRights   )
16223                 *p++ = boards[move][CASTLING][3] + AAA;
16224            if(boards[move][CASTLING][5] != NoRights &&
16225               boards[move][CASTLING][4] != NoRights   )
16226                 *p++ = boards[move][CASTLING][4] + AAA;
16227      } else {
16228
16229         /* [HGM] write true castling rights */
16230         if( nrCastlingRights == 6 ) {
16231             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16232                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16233             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16234                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16235             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16236                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16237             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16238                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16239         }
16240      }
16241      if (q == p) *p++ = '-'; /* No castling rights */
16242      *p++ = ' ';
16243   }
16244
16245   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16246      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16247     /* En passant target square */
16248     if (move > backwardMostMove) {
16249         fromX = moveList[move - 1][0] - AAA;
16250         fromY = moveList[move - 1][1] - ONE;
16251         toX = moveList[move - 1][2] - AAA;
16252         toY = moveList[move - 1][3] - ONE;
16253         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16254             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16255             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16256             fromX == toX) {
16257             /* 2-square pawn move just happened */
16258             *p++ = toX + AAA;
16259             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16260         } else {
16261             *p++ = '-';
16262         }
16263     } else if(move == backwardMostMove) {
16264         // [HGM] perhaps we should always do it like this, and forget the above?
16265         if((signed char)boards[move][EP_STATUS] >= 0) {
16266             *p++ = boards[move][EP_STATUS] + AAA;
16267             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16268         } else {
16269             *p++ = '-';
16270         }
16271     } else {
16272         *p++ = '-';
16273     }
16274     *p++ = ' ';
16275   }
16276   }
16277
16278     /* [HGM] find reversible plies */
16279     {   int i = 0, j=move;
16280
16281         if (appData.debugMode) { int k;
16282             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16283             for(k=backwardMostMove; k<=forwardMostMove; k++)
16284                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16285
16286         }
16287
16288         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16289         if( j == backwardMostMove ) i += initialRulePlies;
16290         sprintf(p, "%d ", i);
16291         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16292     }
16293     /* Fullmove number */
16294     sprintf(p, "%d", (move / 2) + 1);
16295
16296     return StrSave(buf);
16297 }
16298
16299 Boolean
16300 ParseFEN(board, blackPlaysFirst, fen)
16301     Board board;
16302      int *blackPlaysFirst;
16303      char *fen;
16304 {
16305     int i, j;
16306     char *p, c;
16307     int emptycount;
16308     ChessSquare piece;
16309
16310     p = fen;
16311
16312     /* [HGM] by default clear Crazyhouse holdings, if present */
16313     if(gameInfo.holdingsWidth) {
16314        for(i=0; i<BOARD_HEIGHT; i++) {
16315            board[i][0]             = EmptySquare; /* black holdings */
16316            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16317            board[i][1]             = (ChessSquare) 0; /* black counts */
16318            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16319        }
16320     }
16321
16322     /* Piece placement data */
16323     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16324         j = 0;
16325         for (;;) {
16326             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16327                 if (*p == '/') p++;
16328                 emptycount = gameInfo.boardWidth - j;
16329                 while (emptycount--)
16330                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16331                 break;
16332 #if(BOARD_FILES >= 10)
16333             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16334                 p++; emptycount=10;
16335                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16336                 while (emptycount--)
16337                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16338 #endif
16339             } else if (isdigit(*p)) {
16340                 emptycount = *p++ - '0';
16341                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16342                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16343                 while (emptycount--)
16344                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16345             } else if (*p == '+' || isalpha(*p)) {
16346                 if (j >= gameInfo.boardWidth) return FALSE;
16347                 if(*p=='+') {
16348                     piece = CharToPiece(*++p);
16349                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16350                     piece = (ChessSquare) (PROMOTED piece ); p++;
16351                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16352                 } else piece = CharToPiece(*p++);
16353
16354                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16355                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16356                     piece = (ChessSquare) (PROMOTED piece);
16357                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16358                     p++;
16359                 }
16360                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16361             } else {
16362                 return FALSE;
16363             }
16364         }
16365     }
16366     while (*p == '/' || *p == ' ') p++;
16367
16368     /* [HGM] look for Crazyhouse holdings here */
16369     while(*p==' ') p++;
16370     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16371         if(*p == '[') p++;
16372         if(*p == '-' ) p++; /* empty holdings */ else {
16373             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16374             /* if we would allow FEN reading to set board size, we would   */
16375             /* have to add holdings and shift the board read so far here   */
16376             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16377                 p++;
16378                 if((int) piece >= (int) BlackPawn ) {
16379                     i = (int)piece - (int)BlackPawn;
16380                     i = PieceToNumber((ChessSquare)i);
16381                     if( i >= gameInfo.holdingsSize ) return FALSE;
16382                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16383                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16384                 } else {
16385                     i = (int)piece - (int)WhitePawn;
16386                     i = PieceToNumber((ChessSquare)i);
16387                     if( i >= gameInfo.holdingsSize ) return FALSE;
16388                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16389                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16390                 }
16391             }
16392         }
16393         if(*p == ']') p++;
16394     }
16395
16396     while(*p == ' ') p++;
16397
16398     /* Active color */
16399     c = *p++;
16400     if(appData.colorNickNames) {
16401       if( c == appData.colorNickNames[0] ) c = 'w'; else
16402       if( c == appData.colorNickNames[1] ) c = 'b';
16403     }
16404     switch (c) {
16405       case 'w':
16406         *blackPlaysFirst = FALSE;
16407         break;
16408       case 'b':
16409         *blackPlaysFirst = TRUE;
16410         break;
16411       default:
16412         return FALSE;
16413     }
16414
16415     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16416     /* return the extra info in global variiables             */
16417
16418     /* set defaults in case FEN is incomplete */
16419     board[EP_STATUS] = EP_UNKNOWN;
16420     for(i=0; i<nrCastlingRights; i++ ) {
16421         board[CASTLING][i] =
16422             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16423     }   /* assume possible unless obviously impossible */
16424     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16425     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16426     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16427                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16428     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16429     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16430     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16431                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16432     FENrulePlies = 0;
16433
16434     while(*p==' ') p++;
16435     if(nrCastlingRights) {
16436       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16437           /* castling indicator present, so default becomes no castlings */
16438           for(i=0; i<nrCastlingRights; i++ ) {
16439                  board[CASTLING][i] = NoRights;
16440           }
16441       }
16442       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16443              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16444              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16445              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16446         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16447
16448         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16449             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16450             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16451         }
16452         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16453             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16454         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16455                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16456         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16457                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16458         switch(c) {
16459           case'K':
16460               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16461               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16462               board[CASTLING][2] = whiteKingFile;
16463               break;
16464           case'Q':
16465               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16466               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16467               board[CASTLING][2] = whiteKingFile;
16468               break;
16469           case'k':
16470               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16471               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16472               board[CASTLING][5] = blackKingFile;
16473               break;
16474           case'q':
16475               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16476               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16477               board[CASTLING][5] = blackKingFile;
16478           case '-':
16479               break;
16480           default: /* FRC castlings */
16481               if(c >= 'a') { /* black rights */
16482                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16483                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16484                   if(i == BOARD_RGHT) break;
16485                   board[CASTLING][5] = i;
16486                   c -= AAA;
16487                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16488                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16489                   if(c > i)
16490                       board[CASTLING][3] = c;
16491                   else
16492                       board[CASTLING][4] = c;
16493               } else { /* white rights */
16494                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16495                     if(board[0][i] == WhiteKing) break;
16496                   if(i == BOARD_RGHT) break;
16497                   board[CASTLING][2] = i;
16498                   c -= AAA - 'a' + 'A';
16499                   if(board[0][c] >= WhiteKing) break;
16500                   if(c > i)
16501                       board[CASTLING][0] = c;
16502                   else
16503                       board[CASTLING][1] = c;
16504               }
16505         }
16506       }
16507       for(i=0; i<nrCastlingRights; i++)
16508         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16509     if (appData.debugMode) {
16510         fprintf(debugFP, "FEN castling rights:");
16511         for(i=0; i<nrCastlingRights; i++)
16512         fprintf(debugFP, " %d", board[CASTLING][i]);
16513         fprintf(debugFP, "\n");
16514     }
16515
16516       while(*p==' ') p++;
16517     }
16518
16519     /* read e.p. field in games that know e.p. capture */
16520     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16521        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16522       if(*p=='-') {
16523         p++; board[EP_STATUS] = EP_NONE;
16524       } else {
16525          char c = *p++ - AAA;
16526
16527          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16528          if(*p >= '0' && *p <='9') p++;
16529          board[EP_STATUS] = c;
16530       }
16531     }
16532
16533
16534     if(sscanf(p, "%d", &i) == 1) {
16535         FENrulePlies = i; /* 50-move ply counter */
16536         /* (The move number is still ignored)    */
16537     }
16538
16539     return TRUE;
16540 }
16541
16542 void
16543 EditPositionPasteFEN(char *fen)
16544 {
16545   if (fen != NULL) {
16546     Board initial_position;
16547
16548     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16549       DisplayError(_("Bad FEN position in clipboard"), 0);
16550       return ;
16551     } else {
16552       int savedBlackPlaysFirst = blackPlaysFirst;
16553       EditPositionEvent();
16554       blackPlaysFirst = savedBlackPlaysFirst;
16555       CopyBoard(boards[0], initial_position);
16556       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16557       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16558       DisplayBothClocks();
16559       DrawPosition(FALSE, boards[currentMove]);
16560     }
16561   }
16562 }
16563
16564 static char cseq[12] = "\\   ";
16565
16566 Boolean set_cont_sequence(char *new_seq)
16567 {
16568     int len;
16569     Boolean ret;
16570
16571     // handle bad attempts to set the sequence
16572         if (!new_seq)
16573                 return 0; // acceptable error - no debug
16574
16575     len = strlen(new_seq);
16576     ret = (len > 0) && (len < sizeof(cseq));
16577     if (ret)
16578       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16579     else if (appData.debugMode)
16580       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16581     return ret;
16582 }
16583
16584 /*
16585     reformat a source message so words don't cross the width boundary.  internal
16586     newlines are not removed.  returns the wrapped size (no null character unless
16587     included in source message).  If dest is NULL, only calculate the size required
16588     for the dest buffer.  lp argument indicats line position upon entry, and it's
16589     passed back upon exit.
16590 */
16591 int wrap(char *dest, char *src, int count, int width, int *lp)
16592 {
16593     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16594
16595     cseq_len = strlen(cseq);
16596     old_line = line = *lp;
16597     ansi = len = clen = 0;
16598
16599     for (i=0; i < count; i++)
16600     {
16601         if (src[i] == '\033')
16602             ansi = 1;
16603
16604         // if we hit the width, back up
16605         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16606         {
16607             // store i & len in case the word is too long
16608             old_i = i, old_len = len;
16609
16610             // find the end of the last word
16611             while (i && src[i] != ' ' && src[i] != '\n')
16612             {
16613                 i--;
16614                 len--;
16615             }
16616
16617             // word too long?  restore i & len before splitting it
16618             if ((old_i-i+clen) >= width)
16619             {
16620                 i = old_i;
16621                 len = old_len;
16622             }
16623
16624             // extra space?
16625             if (i && src[i-1] == ' ')
16626                 len--;
16627
16628             if (src[i] != ' ' && src[i] != '\n')
16629             {
16630                 i--;
16631                 if (len)
16632                     len--;
16633             }
16634
16635             // now append the newline and continuation sequence
16636             if (dest)
16637                 dest[len] = '\n';
16638             len++;
16639             if (dest)
16640                 strncpy(dest+len, cseq, cseq_len);
16641             len += cseq_len;
16642             line = cseq_len;
16643             clen = cseq_len;
16644             continue;
16645         }
16646
16647         if (dest)
16648             dest[len] = src[i];
16649         len++;
16650         if (!ansi)
16651             line++;
16652         if (src[i] == '\n')
16653             line = 0;
16654         if (src[i] == 'm')
16655             ansi = 0;
16656     }
16657     if (dest && appData.debugMode)
16658     {
16659         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16660             count, width, line, len, *lp);
16661         show_bytes(debugFP, src, count);
16662         fprintf(debugFP, "\ndest: ");
16663         show_bytes(debugFP, dest, len);
16664         fprintf(debugFP, "\n");
16665     }
16666     *lp = dest ? line : old_line;
16667
16668     return len;
16669 }
16670
16671 // [HGM] vari: routines for shelving variations
16672 Boolean modeRestore = FALSE;
16673
16674 void
16675 PushInner(int firstMove, int lastMove)
16676 {
16677         int i, j, nrMoves = lastMove - firstMove;
16678
16679         // push current tail of game on stack
16680         savedResult[storedGames] = gameInfo.result;
16681         savedDetails[storedGames] = gameInfo.resultDetails;
16682         gameInfo.resultDetails = NULL;
16683         savedFirst[storedGames] = firstMove;
16684         savedLast [storedGames] = lastMove;
16685         savedFramePtr[storedGames] = framePtr;
16686         framePtr -= nrMoves; // reserve space for the boards
16687         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16688             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16689             for(j=0; j<MOVE_LEN; j++)
16690                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16691             for(j=0; j<2*MOVE_LEN; j++)
16692                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16693             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16694             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16695             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16696             pvInfoList[firstMove+i-1].depth = 0;
16697             commentList[framePtr+i] = commentList[firstMove+i];
16698             commentList[firstMove+i] = NULL;
16699         }
16700
16701         storedGames++;
16702         forwardMostMove = firstMove; // truncate game so we can start variation
16703 }
16704
16705 void
16706 PushTail(int firstMove, int lastMove)
16707 {
16708         if(appData.icsActive) { // only in local mode
16709                 forwardMostMove = currentMove; // mimic old ICS behavior
16710                 return;
16711         }
16712         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16713
16714         PushInner(firstMove, lastMove);
16715         if(storedGames == 1) GreyRevert(FALSE);
16716         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16717 }
16718
16719 void
16720 PopInner(Boolean annotate)
16721 {
16722         int i, j, nrMoves;
16723         char buf[8000], moveBuf[20];
16724
16725         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16726         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16727         nrMoves = savedLast[storedGames] - currentMove;
16728         if(annotate) {
16729                 int cnt = 10;
16730                 if(!WhiteOnMove(currentMove))
16731                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16732                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16733                 for(i=currentMove; i<forwardMostMove; i++) {
16734                         if(WhiteOnMove(i))
16735                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16736                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16737                         strcat(buf, moveBuf);
16738                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16739                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16740                 }
16741                 strcat(buf, ")");
16742         }
16743         for(i=1; i<=nrMoves; i++) { // copy last variation back
16744             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16745             for(j=0; j<MOVE_LEN; j++)
16746                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16747             for(j=0; j<2*MOVE_LEN; j++)
16748                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16749             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16750             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16751             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16752             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16753             commentList[currentMove+i] = commentList[framePtr+i];
16754             commentList[framePtr+i] = NULL;
16755         }
16756         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16757         framePtr = savedFramePtr[storedGames];
16758         gameInfo.result = savedResult[storedGames];
16759         if(gameInfo.resultDetails != NULL) {
16760             free(gameInfo.resultDetails);
16761       }
16762         gameInfo.resultDetails = savedDetails[storedGames];
16763         forwardMostMove = currentMove + nrMoves;
16764 }
16765
16766 Boolean
16767 PopTail(Boolean annotate)
16768 {
16769         if(appData.icsActive) return FALSE; // only in local mode
16770         if(!storedGames) return FALSE; // sanity
16771         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16772
16773         PopInner(annotate);
16774         if(currentMove < forwardMostMove) ForwardEvent(); else
16775         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16776
16777         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16778         return TRUE;
16779 }
16780
16781 void
16782 CleanupTail()
16783 {       // remove all shelved variations
16784         int i;
16785         for(i=0; i<storedGames; i++) {
16786             if(savedDetails[i])
16787                 free(savedDetails[i]);
16788             savedDetails[i] = NULL;
16789         }
16790         for(i=framePtr; i<MAX_MOVES; i++) {
16791                 if(commentList[i]) free(commentList[i]);
16792                 commentList[i] = NULL;
16793         }
16794         framePtr = MAX_MOVES-1;
16795         storedGames = 0;
16796 }
16797
16798 void
16799 LoadVariation(int index, char *text)
16800 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16801         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16802         int level = 0, move;
16803
16804         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16805         // first find outermost bracketing variation
16806         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16807             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16808                 if(*p == '{') wait = '}'; else
16809                 if(*p == '[') wait = ']'; else
16810                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16811                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16812             }
16813             if(*p == wait) wait = NULLCHAR; // closing ]} found
16814             p++;
16815         }
16816         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16817         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16818         end[1] = NULLCHAR; // clip off comment beyond variation
16819         ToNrEvent(currentMove-1);
16820         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16821         // kludge: use ParsePV() to append variation to game
16822         move = currentMove;
16823         ParsePV(start, TRUE, TRUE);
16824         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16825         ClearPremoveHighlights();
16826         CommentPopDown();
16827         ToNrEvent(currentMove+1);
16828 }
16829