3a77cf4dd1ef9e1d9aa257ed0b72f2f50a1a8f71
[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, WhiteLance, WhiteQueen,
622         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
624         BlackKing, BlackLance, 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         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
883
884 void
885 Load(ChessProgramState *cps, int i)
886 {
887     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
888     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
889         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
890         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
891         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
892         ParseArgsFromString(buf);
893         SwapEngines(i);
894         ReplaceEngine(cps, i);
895         return;
896     }
897     p = engineName;
898     while(q = strchr(p, SLASH)) p = q+1;
899     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
900     if(engineDir[0] != NULLCHAR)
901         appData.directory[i] = engineDir;
902     else if(p != engineName) { // derive directory from engine path, when not given
903         p[-1] = 0;
904         appData.directory[i] = strdup(engineName);
905         p[-1] = SLASH;
906     } else appData.directory[i] = ".";
907     if(params[0]) {
908         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
909         snprintf(command, MSG_SIZ, "%s %s", p, params);
910         p = command;
911     }
912     appData.chessProgram[i] = strdup(p);
913     appData.isUCI[i] = isUCI;
914     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
915     appData.hasOwnBookUCI[i] = hasBook;
916     if(!nickName[0]) useNick = FALSE;
917     if(useNick) ASSIGN(appData.pgnName[i], nickName);
918     if(addToList) {
919         int len;
920         char quote;
921         q = firstChessProgramNames;
922         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
923         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
925                         quote, p, quote, appData.directory[i], 
926                         useNick ? " -fn \"" : "",
927                         useNick ? nickName : "",
928                         useNick ? "\"" : "",
929                         v1 ? " -firstProtocolVersion 1" : "",
930                         hasBook ? "" : " -fNoOwnBookUCI",
931                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
932                         storeVariant ? " -variant " : "",
933                         storeVariant ? VariantName(gameInfo.variant) : "");
934         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
935         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
936         if(q)   free(q);
937     }
938     ReplaceEngine(cps, i);
939 }
940
941 void
942 InitTimeControls()
943 {
944     int matched, min, sec;
945     /*
946      * Parse timeControl resource
947      */
948     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
949                           appData.movesPerSession)) {
950         char buf[MSG_SIZ];
951         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
952         DisplayFatalError(buf, 0, 2);
953     }
954
955     /*
956      * Parse searchTime resource
957      */
958     if (*appData.searchTime != NULLCHAR) {
959         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960         if (matched == 1) {
961             searchTime = min * 60;
962         } else if (matched == 2) {
963             searchTime = min * 60 + sec;
964         } else {
965             char buf[MSG_SIZ];
966             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
967             DisplayFatalError(buf, 0, 2);
968         }
969     }
970 }
971
972 void
973 InitBackEnd1()
974 {
975
976     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
977     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978
979     GetTimeMark(&programStartTime);
980     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
981     appData.seedBase = random() + (random()<<15);
982     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
983
984     ClearProgramStats();
985     programStats.ok_to_send = 1;
986     programStats.seen_stat = 0;
987
988     /*
989      * Initialize game list
990      */
991     ListNew(&gameList);
992
993
994     /*
995      * Internet chess server status
996      */
997     if (appData.icsActive) {
998         appData.matchMode = FALSE;
999         appData.matchGames = 0;
1000 #if ZIPPY
1001         appData.noChessProgram = !appData.zippyPlay;
1002 #else
1003         appData.zippyPlay = FALSE;
1004         appData.zippyTalk = FALSE;
1005         appData.noChessProgram = TRUE;
1006 #endif
1007         if (*appData.icsHelper != NULLCHAR) {
1008             appData.useTelnet = TRUE;
1009             appData.telnetProgram = appData.icsHelper;
1010         }
1011     } else {
1012         appData.zippyTalk = appData.zippyPlay = FALSE;
1013     }
1014
1015     /* [AS] Initialize pv info list [HGM] and game state */
1016     {
1017         int i, j;
1018
1019         for( i=0; i<=framePtr; i++ ) {
1020             pvInfoList[i].depth = -1;
1021             boards[i][EP_STATUS] = EP_NONE;
1022             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1023         }
1024     }
1025
1026     InitTimeControls();
1027
1028     /* [AS] Adjudication threshold */
1029     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030
1031     InitEngine(&first, 0);
1032     InitEngine(&second, 1);
1033     CommonEngineInit();
1034
1035     pairing.which = "pairing"; // pairing engine
1036     pairing.pr = NoProc;
1037     pairing.isr = NULL;
1038     pairing.program = appData.pairingEngine;
1039     pairing.host = "localhost";
1040     pairing.dir = ".";
1041
1042     if (appData.icsActive) {
1043         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1044     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1045         appData.clockMode = FALSE;
1046         first.sendTime = second.sendTime = 0;
1047     }
1048
1049 #if ZIPPY
1050     /* Override some settings from environment variables, for backward
1051        compatibility.  Unfortunately it's not feasible to have the env
1052        vars just set defaults, at least in xboard.  Ugh.
1053     */
1054     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1055       ZippyInit();
1056     }
1057 #endif
1058
1059     if (!appData.icsActive) {
1060       char buf[MSG_SIZ];
1061       int len;
1062
1063       /* Check for variants that are supported only in ICS mode,
1064          or not at all.  Some that are accepted here nevertheless
1065          have bugs; see comments below.
1066       */
1067       VariantClass variant = StringToVariant(appData.variant);
1068       switch (variant) {
1069       case VariantBughouse:     /* need four players and two boards */
1070       case VariantKriegspiel:   /* need to hide pieces and move details */
1071         /* case VariantFischeRandom: (Fabien: moved below) */
1072         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1073         if( (len > MSG_SIZ) && appData.debugMode )
1074           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075
1076         DisplayFatalError(buf, 0, 2);
1077         return;
1078
1079       case VariantUnknown:
1080       case VariantLoadable:
1081       case Variant29:
1082       case Variant30:
1083       case Variant31:
1084       case Variant32:
1085       case Variant33:
1086       case Variant34:
1087       case Variant35:
1088       case Variant36:
1089       default:
1090         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1091         if( (len > MSG_SIZ) && appData.debugMode )
1092           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093
1094         DisplayFatalError(buf, 0, 2);
1095         return;
1096
1097       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1098       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1099       case VariantGothic:     /* [HGM] should work */
1100       case VariantCapablanca: /* [HGM] should work */
1101       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1102       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1103       case VariantKnightmate: /* [HGM] should work */
1104       case VariantCylinder:   /* [HGM] untested */
1105       case VariantFalcon:     /* [HGM] untested */
1106       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1107                                  offboard interposition not understood */
1108       case VariantNormal:     /* definitely works! */
1109       case VariantWildCastle: /* pieces not automatically shuffled */
1110       case VariantNoCastle:   /* pieces not automatically shuffled */
1111       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1112       case VariantLosers:     /* should work except for win condition,
1113                                  and doesn't know captures are mandatory */
1114       case VariantSuicide:    /* should work except for win condition,
1115                                  and doesn't know captures are mandatory */
1116       case VariantGiveaway:   /* should work except for win condition,
1117                                  and doesn't know captures are mandatory */
1118       case VariantTwoKings:   /* should work */
1119       case VariantAtomic:     /* should work except for win condition */
1120       case Variant3Check:     /* should work except for win condition */
1121       case VariantShatranj:   /* should work except for all win conditions */
1122       case VariantMakruk:     /* should work except for draw countdown */
1123       case VariantBerolina:   /* might work if TestLegality is off */
1124       case VariantCapaRandom: /* should work */
1125       case VariantJanus:      /* should work */
1126       case VariantSuper:      /* experimental */
1127       case VariantGreat:      /* experimental, requires legality testing to be off */
1128       case VariantSChess:     /* S-Chess, should work */
1129       case VariantGrand:      /* should work */
1130       case VariantSpartan:    /* should work */
1131         break;
1132       }
1133     }
1134
1135 }
1136
1137 int NextIntegerFromString( char ** str, long * value )
1138 {
1139     int result = -1;
1140     char * s = *str;
1141
1142     while( *s == ' ' || *s == '\t' ) {
1143         s++;
1144     }
1145
1146     *value = 0;
1147
1148     if( *s >= '0' && *s <= '9' ) {
1149         while( *s >= '0' && *s <= '9' ) {
1150             *value = *value * 10 + (*s - '0');
1151             s++;
1152         }
1153
1154         result = 0;
1155     }
1156
1157     *str = s;
1158
1159     return result;
1160 }
1161
1162 int NextTimeControlFromString( char ** str, long * value )
1163 {
1164     long temp;
1165     int result = NextIntegerFromString( str, &temp );
1166
1167     if( result == 0 ) {
1168         *value = temp * 60; /* Minutes */
1169         if( **str == ':' ) {
1170             (*str)++;
1171             result = NextIntegerFromString( str, &temp );
1172             *value += temp; /* Seconds */
1173         }
1174     }
1175
1176     return result;
1177 }
1178
1179 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1180 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1181     int result = -1, type = 0; long temp, temp2;
1182
1183     if(**str != ':') return -1; // old params remain in force!
1184     (*str)++;
1185     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1186     if( NextIntegerFromString( str, &temp ) ) return -1;
1187     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1188
1189     if(**str != '/') {
1190         /* time only: incremental or sudden-death time control */
1191         if(**str == '+') { /* increment follows; read it */
1192             (*str)++;
1193             if(**str == '!') type = *(*str)++; // Bronstein TC
1194             if(result = NextIntegerFromString( str, &temp2)) return -1;
1195             *inc = temp2 * 1000;
1196             if(**str == '.') { // read fraction of increment
1197                 char *start = ++(*str);
1198                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199                 temp2 *= 1000;
1200                 while(start++ < *str) temp2 /= 10;
1201                 *inc += temp2;
1202             }
1203         } else *inc = 0;
1204         *moves = 0; *tc = temp * 1000; *incType = type;
1205         return 0;
1206     }
1207
1208     (*str)++; /* classical time control */
1209     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210
1211     if(result == 0) {
1212         *moves = temp;
1213         *tc    = temp2 * 1000;
1214         *inc   = 0;
1215         *incType = type;
1216     }
1217     return result;
1218 }
1219
1220 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1221 {   /* [HGM] get time to add from the multi-session time-control string */
1222     int incType, moves=1; /* kludge to force reading of first session */
1223     long time, increment;
1224     char *s = tcString;
1225
1226     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1227     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228     do {
1229         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1230         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1231         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1232         if(movenr == -1) return time;    /* last move before new session     */
1233         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1234         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1235         if(!moves) return increment;     /* current session is incremental   */
1236         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1237     } while(movenr >= -1);               /* try again for next session       */
1238
1239     return 0; // no new time quota on this move
1240 }
1241
1242 int
1243 ParseTimeControl(tc, ti, mps)
1244      char *tc;
1245      float ti;
1246      int mps;
1247 {
1248   long tc1;
1249   long tc2;
1250   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1251   int min, sec=0;
1252
1253   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1254   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1255       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1256   if(ti > 0) {
1257
1258     if(mps)
1259       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260     else 
1261       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1262   } else {
1263     if(mps)
1264       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265     else 
1266       snprintf(buf, MSG_SIZ, ":%s", mytc);
1267   }
1268   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269   
1270   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1271     return FALSE;
1272   }
1273
1274   if( *tc == '/' ) {
1275     /* Parse second time control */
1276     tc++;
1277
1278     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1279       return FALSE;
1280     }
1281
1282     if( tc2 == 0 ) {
1283       return FALSE;
1284     }
1285
1286     timeControl_2 = tc2 * 1000;
1287   }
1288   else {
1289     timeControl_2 = 0;
1290   }
1291
1292   if( tc1 == 0 ) {
1293     return FALSE;
1294   }
1295
1296   timeControl = tc1 * 1000;
1297
1298   if (ti >= 0) {
1299     timeIncrement = ti * 1000;  /* convert to ms */
1300     movesPerSession = 0;
1301   } else {
1302     timeIncrement = 0;
1303     movesPerSession = mps;
1304   }
1305   return TRUE;
1306 }
1307
1308 void
1309 InitBackEnd2()
1310 {
1311     if (appData.debugMode) {
1312         fprintf(debugFP, "%s\n", programVersion);
1313     }
1314
1315     set_cont_sequence(appData.wrapContSeq);
1316     if (appData.matchGames > 0) {
1317         appData.matchMode = TRUE;
1318     } else if (appData.matchMode) {
1319         appData.matchGames = 1;
1320     }
1321     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1322         appData.matchGames = appData.sameColorGames;
1323     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1324         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1325         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1326     }
1327     Reset(TRUE, FALSE);
1328     if (appData.noChessProgram || first.protocolVersion == 1) {
1329       InitBackEnd3();
1330     } else {
1331       /* kludge: allow timeout for initial "feature" commands */
1332       FreezeUI();
1333       DisplayMessage("", _("Starting chess program"));
1334       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1335     }
1336 }
1337
1338 int
1339 CalculateIndex(int index, int gameNr)
1340 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341     int res;
1342     if(index > 0) return index; // fixed nmber
1343     if(index == 0) return 1;
1344     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1345     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1346     return res;
1347 }
1348
1349 int
1350 LoadGameOrPosition(int gameNr)
1351 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1352     if (*appData.loadGameFile != NULLCHAR) {
1353         if (!LoadGameFromFile(appData.loadGameFile,
1354                 CalculateIndex(appData.loadGameIndex, gameNr),
1355                               appData.loadGameFile, FALSE)) {
1356             DisplayFatalError(_("Bad game file"), 0, 1);
1357             return 0;
1358         }
1359     } else if (*appData.loadPositionFile != NULLCHAR) {
1360         if (!LoadPositionFromFile(appData.loadPositionFile,
1361                 CalculateIndex(appData.loadPositionIndex, gameNr),
1362                                   appData.loadPositionFile)) {
1363             DisplayFatalError(_("Bad position file"), 0, 1);
1364             return 0;
1365         }
1366     }
1367     return 1;
1368 }
1369
1370 void
1371 ReserveGame(int gameNr, char resChar)
1372 {
1373     FILE *tf = fopen(appData.tourneyFile, "r+");
1374     char *p, *q, c, buf[MSG_SIZ];
1375     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1376     safeStrCpy(buf, lastMsg, MSG_SIZ);
1377     DisplayMessage(_("Pick new game"), "");
1378     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1379     ParseArgsFromFile(tf);
1380     p = q = appData.results;
1381     if(appData.debugMode) {
1382       char *r = appData.participants;
1383       fprintf(debugFP, "results = '%s'\n", p);
1384       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1385       fprintf(debugFP, "\n");
1386     }
1387     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388     nextGame = q - p;
1389     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1390     safeStrCpy(q, p, strlen(p) + 2);
1391     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1392     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1393     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1394         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1395         q[nextGame] = '*';
1396     }
1397     fseek(tf, -(strlen(p)+4), SEEK_END);
1398     c = fgetc(tf);
1399     if(c != '"') // depending on DOS or Unix line endings we can be one off
1400          fseek(tf, -(strlen(p)+2), SEEK_END);
1401     else fseek(tf, -(strlen(p)+3), SEEK_END);
1402     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1403     DisplayMessage(buf, "");
1404     free(p); appData.results = q;
1405     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1406        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1407         UnloadEngine(&first);  // next game belongs to other pairing;
1408         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1409     }
1410 }
1411
1412 void
1413 MatchEvent(int mode)
1414 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415         int dummy;
1416         if(matchMode) { // already in match mode: switch it off
1417             abortMatch = TRUE;
1418             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1419             return;
1420         }
1421 //      if(gameMode != BeginningOfGame) {
1422 //          DisplayError(_("You can only start a match from the initial position."), 0);
1423 //          return;
1424 //      }
1425         abortMatch = FALSE;
1426         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1427         /* Set up machine vs. machine match */
1428         nextGame = 0;
1429         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1430         if(appData.tourneyFile[0]) {
1431             ReserveGame(-1, 0);
1432             if(nextGame > appData.matchGames) {
1433                 char buf[MSG_SIZ];
1434                 if(strchr(appData.results, '*') == NULL) {
1435                     FILE *f;
1436                     appData.tourneyCycles++;
1437                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438                         fclose(f);
1439                         NextTourneyGame(-1, &dummy);
1440                         ReserveGame(-1, 0);
1441                         if(nextGame <= appData.matchGames) {
1442                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443                             matchMode = mode;
1444                             ScheduleDelayedEvent(NextMatchGame, 10000);
1445                             return;
1446                         }
1447                     }
1448                 }
1449                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1450                 DisplayError(buf, 0);
1451                 appData.tourneyFile[0] = 0;
1452                 return;
1453             }
1454         } else
1455         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1456             DisplayFatalError(_("Can't have a match with no chess programs"),
1457                               0, 2);
1458             return;
1459         }
1460         matchMode = mode;
1461         matchGame = roundNr = 1;
1462         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1463         NextMatchGame();
1464 }
1465
1466 void
1467 InitBackEnd3 P((void))
1468 {
1469     GameMode initialMode;
1470     char buf[MSG_SIZ];
1471     int err, len;
1472
1473     InitChessProgram(&first, startedFromSetupPosition);
1474
1475     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1476         free(programVersion);
1477         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1478         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1479     }
1480
1481     if (appData.icsActive) {
1482 #ifdef WIN32
1483         /* [DM] Make a console window if needed [HGM] merged ifs */
1484         ConsoleCreate();
1485 #endif
1486         err = establish();
1487         if (err != 0)
1488           {
1489             if (*appData.icsCommPort != NULLCHAR)
1490               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1491                              appData.icsCommPort);
1492             else
1493               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1494                         appData.icsHost, appData.icsPort);
1495
1496             if( (len > MSG_SIZ) && appData.debugMode )
1497               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498
1499             DisplayFatalError(buf, err, 1);
1500             return;
1501         }
1502         SetICSMode();
1503         telnetISR =
1504           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505         fromUserISR =
1506           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1507         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1508             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1509     } else if (appData.noChessProgram) {
1510         SetNCPMode();
1511     } else {
1512         SetGNUMode();
1513     }
1514
1515     if (*appData.cmailGameName != NULLCHAR) {
1516         SetCmailMode();
1517         OpenLoopback(&cmailPR);
1518         cmailISR =
1519           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1520     }
1521
1522     ThawUI();
1523     DisplayMessage("", "");
1524     if (StrCaseCmp(appData.initialMode, "") == 0) {
1525       initialMode = BeginningOfGame;
1526       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1527         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1528         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1529         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1530         ModeHighlight();
1531       }
1532     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1533       initialMode = TwoMachinesPlay;
1534     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1535       initialMode = AnalyzeFile;
1536     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1537       initialMode = AnalyzeMode;
1538     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1539       initialMode = MachinePlaysWhite;
1540     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1541       initialMode = MachinePlaysBlack;
1542     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1543       initialMode = EditGame;
1544     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1545       initialMode = EditPosition;
1546     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1547       initialMode = Training;
1548     } else {
1549       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1550       if( (len > MSG_SIZ) && appData.debugMode )
1551         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552
1553       DisplayFatalError(buf, 0, 2);
1554       return;
1555     }
1556
1557     if (appData.matchMode) {
1558         if(appData.tourneyFile[0]) { // start tourney from command line
1559             FILE *f;
1560             if(f = fopen(appData.tourneyFile, "r")) {
1561                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562                 fclose(f);
1563                 appData.clockMode = TRUE;
1564                 SetGNUMode();
1565             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1566         }
1567         MatchEvent(TRUE);
1568     } else if (*appData.cmailGameName != NULLCHAR) {
1569         /* Set up cmail mode */
1570         ReloadCmailMsgEvent(TRUE);
1571     } else {
1572         /* Set up other modes */
1573         if (initialMode == AnalyzeFile) {
1574           if (*appData.loadGameFile == NULLCHAR) {
1575             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1576             return;
1577           }
1578         }
1579         if (*appData.loadGameFile != NULLCHAR) {
1580             (void) LoadGameFromFile(appData.loadGameFile,
1581                                     appData.loadGameIndex,
1582                                     appData.loadGameFile, TRUE);
1583         } else if (*appData.loadPositionFile != NULLCHAR) {
1584             (void) LoadPositionFromFile(appData.loadPositionFile,
1585                                         appData.loadPositionIndex,
1586                                         appData.loadPositionFile);
1587             /* [HGM] try to make self-starting even after FEN load */
1588             /* to allow automatic setup of fairy variants with wtm */
1589             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1590                 gameMode = BeginningOfGame;
1591                 setboardSpoiledMachineBlack = 1;
1592             }
1593             /* [HGM] loadPos: make that every new game uses the setup */
1594             /* from file as long as we do not switch variant          */
1595             if(!blackPlaysFirst) {
1596                 startedFromPositionFile = TRUE;
1597                 CopyBoard(filePosition, boards[0]);
1598             }
1599         }
1600         if (initialMode == AnalyzeMode) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1603             return;
1604           }
1605           if (appData.icsActive) {
1606             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1607             return;
1608           }
1609           AnalyzeModeEvent();
1610         } else if (initialMode == AnalyzeFile) {
1611           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1612           ShowThinkingEvent();
1613           AnalyzeFileEvent();
1614           AnalysisPeriodicEvent(1);
1615         } else if (initialMode == MachinePlaysWhite) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineWhiteEvent();
1627         } else if (initialMode == MachinePlaysBlack) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           MachineBlackEvent();
1639         } else if (initialMode == TwoMachinesPlay) {
1640           if (appData.noChessProgram) {
1641             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1642                               0, 2);
1643             return;
1644           }
1645           if (appData.icsActive) {
1646             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1647                               0, 2);
1648             return;
1649           }
1650           TwoMachinesEvent();
1651         } else if (initialMode == EditGame) {
1652           EditGameEvent();
1653         } else if (initialMode == EditPosition) {
1654           EditPositionEvent();
1655         } else if (initialMode == Training) {
1656           if (*appData.loadGameFile == NULLCHAR) {
1657             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658             return;
1659           }
1660           TrainingEvent();
1661         }
1662     }
1663 }
1664
1665 void
1666 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 {
1668     DisplayBook(current+1);
1669
1670     MoveHistorySet( movelist, first, last, current, pvInfoList );
1671
1672     EvalGraphSet( first, last, current, pvInfoList );
1673
1674     MakeEngineOutputTitle();
1675 }
1676
1677 /*
1678  * Establish will establish a contact to a remote host.port.
1679  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1680  *  used to talk to the host.
1681  * Returns 0 if okay, error code if not.
1682  */
1683 int
1684 establish()
1685 {
1686     char buf[MSG_SIZ];
1687
1688     if (*appData.icsCommPort != NULLCHAR) {
1689         /* Talk to the host through a serial comm port */
1690         return OpenCommPort(appData.icsCommPort, &icsPR);
1691
1692     } else if (*appData.gateway != NULLCHAR) {
1693         if (*appData.remoteShell == NULLCHAR) {
1694             /* Use the rcmd protocol to run telnet program on a gateway host */
1695             snprintf(buf, sizeof(buf), "%s %s %s",
1696                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1697             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1698
1699         } else {
1700             /* Use the rsh program to run telnet program on a gateway host */
1701             if (*appData.remoteUser == NULLCHAR) {
1702                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1703                         appData.gateway, appData.telnetProgram,
1704                         appData.icsHost, appData.icsPort);
1705             } else {
1706                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1707                         appData.remoteShell, appData.gateway,
1708                         appData.remoteUser, appData.telnetProgram,
1709                         appData.icsHost, appData.icsPort);
1710             }
1711             return StartChildProcess(buf, "", &icsPR);
1712
1713         }
1714     } else if (appData.useTelnet) {
1715         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1716
1717     } else {
1718         /* TCP socket interface differs somewhat between
1719            Unix and NT; handle details in the front end.
1720            */
1721         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1722     }
1723 }
1724
1725 void EscapeExpand(char *p, char *q)
1726 {       // [HGM] initstring: routine to shape up string arguments
1727         while(*p++ = *q++) if(p[-1] == '\\')
1728             switch(*q++) {
1729                 case 'n': p[-1] = '\n'; break;
1730                 case 'r': p[-1] = '\r'; break;
1731                 case 't': p[-1] = '\t'; break;
1732                 case '\\': p[-1] = '\\'; break;
1733                 case 0: *p = 0; return;
1734                 default: p[-1] = q[-1]; break;
1735             }
1736 }
1737
1738 void
1739 show_bytes(fp, buf, count)
1740      FILE *fp;
1741      char *buf;
1742      int count;
1743 {
1744     while (count--) {
1745         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1746             fprintf(fp, "\\%03o", *buf & 0xff);
1747         } else {
1748             putc(*buf, fp);
1749         }
1750         buf++;
1751     }
1752     fflush(fp);
1753 }
1754
1755 /* Returns an errno value */
1756 int
1757 OutputMaybeTelnet(pr, message, count, outError)
1758      ProcRef pr;
1759      char *message;
1760      int count;
1761      int *outError;
1762 {
1763     char buf[8192], *p, *q, *buflim;
1764     int left, newcount, outcount;
1765
1766     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1767         *appData.gateway != NULLCHAR) {
1768         if (appData.debugMode) {
1769             fprintf(debugFP, ">ICS: ");
1770             show_bytes(debugFP, message, count);
1771             fprintf(debugFP, "\n");
1772         }
1773         return OutputToProcess(pr, message, count, outError);
1774     }
1775
1776     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1777     p = message;
1778     q = buf;
1779     left = count;
1780     newcount = 0;
1781     while (left) {
1782         if (q >= buflim) {
1783             if (appData.debugMode) {
1784                 fprintf(debugFP, ">ICS: ");
1785                 show_bytes(debugFP, buf, newcount);
1786                 fprintf(debugFP, "\n");
1787             }
1788             outcount = OutputToProcess(pr, buf, newcount, outError);
1789             if (outcount < newcount) return -1; /* to be sure */
1790             q = buf;
1791             newcount = 0;
1792         }
1793         if (*p == '\n') {
1794             *q++ = '\r';
1795             newcount++;
1796         } else if (((unsigned char) *p) == TN_IAC) {
1797             *q++ = (char) TN_IAC;
1798             newcount ++;
1799         }
1800         *q++ = *p++;
1801         newcount++;
1802         left--;
1803     }
1804     if (appData.debugMode) {
1805         fprintf(debugFP, ">ICS: ");
1806         show_bytes(debugFP, buf, newcount);
1807         fprintf(debugFP, "\n");
1808     }
1809     outcount = OutputToProcess(pr, buf, newcount, outError);
1810     if (outcount < newcount) return -1; /* to be sure */
1811     return count;
1812 }
1813
1814 void
1815 read_from_player(isr, closure, message, count, error)
1816      InputSourceRef isr;
1817      VOIDSTAR closure;
1818      char *message;
1819      int count;
1820      int error;
1821 {
1822     int outError, outCount;
1823     static int gotEof = 0;
1824
1825     /* Pass data read from player on to ICS */
1826     if (count > 0) {
1827         gotEof = 0;
1828         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1829         if (outCount < count) {
1830             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831         }
1832     } else if (count < 0) {
1833         RemoveInputSource(isr);
1834         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1835     } else if (gotEof++ > 0) {
1836         RemoveInputSource(isr);
1837         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1838     }
1839 }
1840
1841 void
1842 KeepAlive()
1843 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1844     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1845     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1846     SendToICS("date\n");
1847     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1848 }
1849
1850 /* added routine for printf style output to ics */
1851 void ics_printf(char *format, ...)
1852 {
1853     char buffer[MSG_SIZ];
1854     va_list args;
1855
1856     va_start(args, format);
1857     vsnprintf(buffer, sizeof(buffer), format, args);
1858     buffer[sizeof(buffer)-1] = '\0';
1859     SendToICS(buffer);
1860     va_end(args);
1861 }
1862
1863 void
1864 SendToICS(s)
1865      char *s;
1866 {
1867     int count, outCount, outError;
1868
1869     if (icsPR == NULL) return;
1870
1871     count = strlen(s);
1872     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1873     if (outCount < count) {
1874         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1875     }
1876 }
1877
1878 /* This is used for sending logon scripts to the ICS. Sending
1879    without a delay causes problems when using timestamp on ICC
1880    (at least on my machine). */
1881 void
1882 SendToICSDelayed(s,msdelay)
1883      char *s;
1884      long msdelay;
1885 {
1886     int count, outCount, outError;
1887
1888     if (icsPR == NULL) return;
1889
1890     count = strlen(s);
1891     if (appData.debugMode) {
1892         fprintf(debugFP, ">ICS: ");
1893         show_bytes(debugFP, s, count);
1894         fprintf(debugFP, "\n");
1895     }
1896     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1897                                       msdelay);
1898     if (outCount < count) {
1899         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900     }
1901 }
1902
1903
1904 /* Remove all highlighting escape sequences in s
1905    Also deletes any suffix starting with '('
1906    */
1907 char *
1908 StripHighlightAndTitle(s)
1909      char *s;
1910 {
1911     static char retbuf[MSG_SIZ];
1912     char *p = retbuf;
1913
1914     while (*s != NULLCHAR) {
1915         while (*s == '\033') {
1916             while (*s != NULLCHAR && !isalpha(*s)) s++;
1917             if (*s != NULLCHAR) s++;
1918         }
1919         while (*s != NULLCHAR && *s != '\033') {
1920             if (*s == '(' || *s == '[') {
1921                 *p = NULLCHAR;
1922                 return retbuf;
1923             }
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 /* Remove all highlighting escape sequences in s */
1932 char *
1933 StripHighlight(s)
1934      char *s;
1935 {
1936     static char retbuf[MSG_SIZ];
1937     char *p = retbuf;
1938
1939     while (*s != NULLCHAR) {
1940         while (*s == '\033') {
1941             while (*s != NULLCHAR && !isalpha(*s)) s++;
1942             if (*s != NULLCHAR) s++;
1943         }
1944         while (*s != NULLCHAR && *s != '\033') {
1945             *p++ = *s++;
1946         }
1947     }
1948     *p = NULLCHAR;
1949     return retbuf;
1950 }
1951
1952 char *variantNames[] = VARIANT_NAMES;
1953 char *
1954 VariantName(v)
1955      VariantClass v;
1956 {
1957     return variantNames[v];
1958 }
1959
1960
1961 /* Identify a variant from the strings the chess servers use or the
1962    PGN Variant tag names we use. */
1963 VariantClass
1964 StringToVariant(e)
1965      char *e;
1966 {
1967     char *p;
1968     int wnum = -1;
1969     VariantClass v = VariantNormal;
1970     int i, found = FALSE;
1971     char buf[MSG_SIZ];
1972     int len;
1973
1974     if (!e) return v;
1975
1976     /* [HGM] skip over optional board-size prefixes */
1977     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1978         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1979         while( *e++ != '_');
1980     }
1981
1982     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1983         v = VariantNormal;
1984         found = TRUE;
1985     } else
1986     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1987       if (StrCaseStr(e, variantNames[i])) {
1988         v = (VariantClass) i;
1989         found = TRUE;
1990         break;
1991       }
1992     }
1993
1994     if (!found) {
1995       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1996           || StrCaseStr(e, "wild/fr")
1997           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1998         v = VariantFischeRandom;
1999       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2000                  (i = 1, p = StrCaseStr(e, "w"))) {
2001         p += i;
2002         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2003         if (isdigit(*p)) {
2004           wnum = atoi(p);
2005         } else {
2006           wnum = -1;
2007         }
2008         switch (wnum) {
2009         case 0: /* FICS only, actually */
2010         case 1:
2011           /* Castling legal even if K starts on d-file */
2012           v = VariantWildCastle;
2013           break;
2014         case 2:
2015         case 3:
2016         case 4:
2017           /* Castling illegal even if K & R happen to start in
2018              normal positions. */
2019           v = VariantNoCastle;
2020           break;
2021         case 5:
2022         case 7:
2023         case 8:
2024         case 10:
2025         case 11:
2026         case 12:
2027         case 13:
2028         case 14:
2029         case 15:
2030         case 18:
2031         case 19:
2032           /* Castling legal iff K & R start in normal positions */
2033           v = VariantNormal;
2034           break;
2035         case 6:
2036         case 20:
2037         case 21:
2038           /* Special wilds for position setup; unclear what to do here */
2039           v = VariantLoadable;
2040           break;
2041         case 9:
2042           /* Bizarre ICC game */
2043           v = VariantTwoKings;
2044           break;
2045         case 16:
2046           v = VariantKriegspiel;
2047           break;
2048         case 17:
2049           v = VariantLosers;
2050           break;
2051         case 22:
2052           v = VariantFischeRandom;
2053           break;
2054         case 23:
2055           v = VariantCrazyhouse;
2056           break;
2057         case 24:
2058           v = VariantBughouse;
2059           break;
2060         case 25:
2061           v = Variant3Check;
2062           break;
2063         case 26:
2064           /* Not quite the same as FICS suicide! */
2065           v = VariantGiveaway;
2066           break;
2067         case 27:
2068           v = VariantAtomic;
2069           break;
2070         case 28:
2071           v = VariantShatranj;
2072           break;
2073
2074         /* Temporary names for future ICC types.  The name *will* change in
2075            the next xboard/WinBoard release after ICC defines it. */
2076         case 29:
2077           v = Variant29;
2078           break;
2079         case 30:
2080           v = Variant30;
2081           break;
2082         case 31:
2083           v = Variant31;
2084           break;
2085         case 32:
2086           v = Variant32;
2087           break;
2088         case 33:
2089           v = Variant33;
2090           break;
2091         case 34:
2092           v = Variant34;
2093           break;
2094         case 35:
2095           v = Variant35;
2096           break;
2097         case 36:
2098           v = Variant36;
2099           break;
2100         case 37:
2101           v = VariantShogi;
2102           break;
2103         case 38:
2104           v = VariantXiangqi;
2105           break;
2106         case 39:
2107           v = VariantCourier;
2108           break;
2109         case 40:
2110           v = VariantGothic;
2111           break;
2112         case 41:
2113           v = VariantCapablanca;
2114           break;
2115         case 42:
2116           v = VariantKnightmate;
2117           break;
2118         case 43:
2119           v = VariantFairy;
2120           break;
2121         case 44:
2122           v = VariantCylinder;
2123           break;
2124         case 45:
2125           v = VariantFalcon;
2126           break;
2127         case 46:
2128           v = VariantCapaRandom;
2129           break;
2130         case 47:
2131           v = VariantBerolina;
2132           break;
2133         case 48:
2134           v = VariantJanus;
2135           break;
2136         case 49:
2137           v = VariantSuper;
2138           break;
2139         case 50:
2140           v = VariantGreat;
2141           break;
2142         case -1:
2143           /* Found "wild" or "w" in the string but no number;
2144              must assume it's normal chess. */
2145           v = VariantNormal;
2146           break;
2147         default:
2148           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2149           if( (len > MSG_SIZ) && appData.debugMode )
2150             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2151
2152           DisplayError(buf, 0);
2153           v = VariantUnknown;
2154           break;
2155         }
2156       }
2157     }
2158     if (appData.debugMode) {
2159       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2160               e, wnum, VariantName(v));
2161     }
2162     return v;
2163 }
2164
2165 static int leftover_start = 0, leftover_len = 0;
2166 char star_match[STAR_MATCH_N][MSG_SIZ];
2167
2168 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2169    advance *index beyond it, and set leftover_start to the new value of
2170    *index; else return FALSE.  If pattern contains the character '*', it
2171    matches any sequence of characters not containing '\r', '\n', or the
2172    character following the '*' (if any), and the matched sequence(s) are
2173    copied into star_match.
2174    */
2175 int
2176 looking_at(buf, index, pattern)
2177      char *buf;
2178      int *index;
2179      char *pattern;
2180 {
2181     char *bufp = &buf[*index], *patternp = pattern;
2182     int star_count = 0;
2183     char *matchp = star_match[0];
2184
2185     for (;;) {
2186         if (*patternp == NULLCHAR) {
2187             *index = leftover_start = bufp - buf;
2188             *matchp = NULLCHAR;
2189             return TRUE;
2190         }
2191         if (*bufp == NULLCHAR) return FALSE;
2192         if (*patternp == '*') {
2193             if (*bufp == *(patternp + 1)) {
2194                 *matchp = NULLCHAR;
2195                 matchp = star_match[++star_count];
2196                 patternp += 2;
2197                 bufp++;
2198                 continue;
2199             } else if (*bufp == '\n' || *bufp == '\r') {
2200                 patternp++;
2201                 if (*patternp == NULLCHAR)
2202                   continue;
2203                 else
2204                   return FALSE;
2205             } else {
2206                 *matchp++ = *bufp++;
2207                 continue;
2208             }
2209         }
2210         if (*patternp != *bufp) return FALSE;
2211         patternp++;
2212         bufp++;
2213     }
2214 }
2215
2216 void
2217 SendToPlayer(data, length)
2218      char *data;
2219      int length;
2220 {
2221     int error, outCount;
2222     outCount = OutputToProcess(NoProc, data, length, &error);
2223     if (outCount < length) {
2224         DisplayFatalError(_("Error writing to display"), error, 1);
2225     }
2226 }
2227
2228 void
2229 PackHolding(packed, holding)
2230      char packed[];
2231      char *holding;
2232 {
2233     char *p = holding;
2234     char *q = packed;
2235     int runlength = 0;
2236     int curr = 9999;
2237     do {
2238         if (*p == curr) {
2239             runlength++;
2240         } else {
2241             switch (runlength) {
2242               case 0:
2243                 break;
2244               case 1:
2245                 *q++ = curr;
2246                 break;
2247               case 2:
2248                 *q++ = curr;
2249                 *q++ = curr;
2250                 break;
2251               default:
2252                 sprintf(q, "%d", runlength);
2253                 while (*q) q++;
2254                 *q++ = curr;
2255                 break;
2256             }
2257             runlength = 1;
2258             curr = *p;
2259         }
2260     } while (*p++);
2261     *q = NULLCHAR;
2262 }
2263
2264 /* Telnet protocol requests from the front end */
2265 void
2266 TelnetRequest(ddww, option)
2267      unsigned char ddww, option;
2268 {
2269     unsigned char msg[3];
2270     int outCount, outError;
2271
2272     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273
2274     if (appData.debugMode) {
2275         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2276         switch (ddww) {
2277           case TN_DO:
2278             ddwwStr = "DO";
2279             break;
2280           case TN_DONT:
2281             ddwwStr = "DONT";
2282             break;
2283           case TN_WILL:
2284             ddwwStr = "WILL";
2285             break;
2286           case TN_WONT:
2287             ddwwStr = "WONT";
2288             break;
2289           default:
2290             ddwwStr = buf1;
2291             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292             break;
2293         }
2294         switch (option) {
2295           case TN_ECHO:
2296             optionStr = "ECHO";
2297             break;
2298           default:
2299             optionStr = buf2;
2300             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301             break;
2302         }
2303         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304     }
2305     msg[0] = TN_IAC;
2306     msg[1] = ddww;
2307     msg[2] = option;
2308     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309     if (outCount < 3) {
2310         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311     }
2312 }
2313
2314 void
2315 DoEcho()
2316 {
2317     if (!appData.icsActive) return;
2318     TelnetRequest(TN_DO, TN_ECHO);
2319 }
2320
2321 void
2322 DontEcho()
2323 {
2324     if (!appData.icsActive) return;
2325     TelnetRequest(TN_DONT, TN_ECHO);
2326 }
2327
2328 void
2329 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2330 {
2331     /* put the holdings sent to us by the server on the board holdings area */
2332     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2333     char p;
2334     ChessSquare piece;
2335
2336     if(gameInfo.holdingsWidth < 2)  return;
2337     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2338         return; // prevent overwriting by pre-board holdings
2339
2340     if( (int)lowestPiece >= BlackPawn ) {
2341         holdingsColumn = 0;
2342         countsColumn = 1;
2343         holdingsStartRow = BOARD_HEIGHT-1;
2344         direction = -1;
2345     } else {
2346         holdingsColumn = BOARD_WIDTH-1;
2347         countsColumn = BOARD_WIDTH-2;
2348         holdingsStartRow = 0;
2349         direction = 1;
2350     }
2351
2352     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2353         board[i][holdingsColumn] = EmptySquare;
2354         board[i][countsColumn]   = (ChessSquare) 0;
2355     }
2356     while( (p=*holdings++) != NULLCHAR ) {
2357         piece = CharToPiece( ToUpper(p) );
2358         if(piece == EmptySquare) continue;
2359         /*j = (int) piece - (int) WhitePawn;*/
2360         j = PieceToNumber(piece);
2361         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2362         if(j < 0) continue;               /* should not happen */
2363         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2364         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2365         board[holdingsStartRow+j*direction][countsColumn]++;
2366     }
2367 }
2368
2369
2370 void
2371 VariantSwitch(Board board, VariantClass newVariant)
2372 {
2373    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2374    static Board oldBoard;
2375
2376    startedFromPositionFile = FALSE;
2377    if(gameInfo.variant == newVariant) return;
2378
2379    /* [HGM] This routine is called each time an assignment is made to
2380     * gameInfo.variant during a game, to make sure the board sizes
2381     * are set to match the new variant. If that means adding or deleting
2382     * holdings, we shift the playing board accordingly
2383     * This kludge is needed because in ICS observe mode, we get boards
2384     * of an ongoing game without knowing the variant, and learn about the
2385     * latter only later. This can be because of the move list we requested,
2386     * in which case the game history is refilled from the beginning anyway,
2387     * but also when receiving holdings of a crazyhouse game. In the latter
2388     * case we want to add those holdings to the already received position.
2389     */
2390
2391
2392    if (appData.debugMode) {
2393      fprintf(debugFP, "Switch board from %s to %s\n",
2394              VariantName(gameInfo.variant), VariantName(newVariant));
2395      setbuf(debugFP, NULL);
2396    }
2397    shuffleOpenings = 0;       /* [HGM] shuffle */
2398    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2399    switch(newVariant)
2400      {
2401      case VariantShogi:
2402        newWidth = 9;  newHeight = 9;
2403        gameInfo.holdingsSize = 7;
2404      case VariantBughouse:
2405      case VariantCrazyhouse:
2406        newHoldingsWidth = 2; break;
2407      case VariantGreat:
2408        newWidth = 10;
2409      case VariantSuper:
2410        newHoldingsWidth = 2;
2411        gameInfo.holdingsSize = 8;
2412        break;
2413      case VariantGothic:
2414      case VariantCapablanca:
2415      case VariantCapaRandom:
2416        newWidth = 10;
2417      default:
2418        newHoldingsWidth = gameInfo.holdingsSize = 0;
2419      };
2420
2421    if(newWidth  != gameInfo.boardWidth  ||
2422       newHeight != gameInfo.boardHeight ||
2423       newHoldingsWidth != gameInfo.holdingsWidth ) {
2424
2425      /* shift position to new playing area, if needed */
2426      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2427        for(i=0; i<BOARD_HEIGHT; i++)
2428          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2429            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430              board[i][j];
2431        for(i=0; i<newHeight; i++) {
2432          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2433          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434        }
2435      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2436        for(i=0; i<BOARD_HEIGHT; i++)
2437          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2438            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439              board[i][j];
2440      }
2441      gameInfo.boardWidth  = newWidth;
2442      gameInfo.boardHeight = newHeight;
2443      gameInfo.holdingsWidth = newHoldingsWidth;
2444      gameInfo.variant = newVariant;
2445      InitDrawingSizes(-2, 0);
2446    } else gameInfo.variant = newVariant;
2447    CopyBoard(oldBoard, board);   // remember correctly formatted board
2448      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2449    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 }
2451
2452 static int loggedOn = FALSE;
2453
2454 /*-- Game start info cache: --*/
2455 int gs_gamenum;
2456 char gs_kind[MSG_SIZ];
2457 static char player1Name[128] = "";
2458 static char player2Name[128] = "";
2459 static char cont_seq[] = "\n\\   ";
2460 static int player1Rating = -1;
2461 static int player2Rating = -1;
2462 /*----------------------------*/
2463
2464 ColorClass curColor = ColorNormal;
2465 int suppressKibitz = 0;
2466
2467 // [HGM] seekgraph
2468 Boolean soughtPending = FALSE;
2469 Boolean seekGraphUp;
2470 #define MAX_SEEK_ADS 200
2471 #define SQUARE 0x80
2472 char *seekAdList[MAX_SEEK_ADS];
2473 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2474 float tcList[MAX_SEEK_ADS];
2475 char colorList[MAX_SEEK_ADS];
2476 int nrOfSeekAds = 0;
2477 int minRating = 1010, maxRating = 2800;
2478 int hMargin = 10, vMargin = 20, h, w;
2479 extern int squareSize, lineGap;
2480
2481 void
2482 PlotSeekAd(int i)
2483 {
2484         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2485         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2486         if(r < minRating+100 && r >=0 ) r = minRating+100;
2487         if(r > maxRating) r = maxRating;
2488         if(tc < 1.) tc = 1.;
2489         if(tc > 95.) tc = 95.;
2490         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2491         y = ((double)r - minRating)/(maxRating - minRating)
2492             * (h-vMargin-squareSize/8-1) + vMargin;
2493         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2494         if(strstr(seekAdList[i], " u ")) color = 1;
2495         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2496            !strstr(seekAdList[i], "bullet") &&
2497            !strstr(seekAdList[i], "blitz") &&
2498            !strstr(seekAdList[i], "standard") ) color = 2;
2499         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2500         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2501 }
2502
2503 void
2504 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2505 {
2506         char buf[MSG_SIZ], *ext = "";
2507         VariantClass v = StringToVariant(type);
2508         if(strstr(type, "wild")) {
2509             ext = type + 4; // append wild number
2510             if(v == VariantFischeRandom) type = "chess960"; else
2511             if(v == VariantLoadable) type = "setup"; else
2512             type = VariantName(v);
2513         }
2514         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2515         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2516             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2517             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2518             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2519             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2520             seekNrList[nrOfSeekAds] = nr;
2521             zList[nrOfSeekAds] = 0;
2522             seekAdList[nrOfSeekAds++] = StrSave(buf);
2523             if(plot) PlotSeekAd(nrOfSeekAds-1);
2524         }
2525 }
2526
2527 void
2528 EraseSeekDot(int i)
2529 {
2530     int x = xList[i], y = yList[i], d=squareSize/4, k;
2531     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2532     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2533     // now replot every dot that overlapped
2534     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2535         int xx = xList[k], yy = yList[k];
2536         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2537             DrawSeekDot(xx, yy, colorList[k]);
2538     }
2539 }
2540
2541 void
2542 RemoveSeekAd(int nr)
2543 {
2544         int i;
2545         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546             EraseSeekDot(i);
2547             if(seekAdList[i]) free(seekAdList[i]);
2548             seekAdList[i] = seekAdList[--nrOfSeekAds];
2549             seekNrList[i] = seekNrList[nrOfSeekAds];
2550             ratingList[i] = ratingList[nrOfSeekAds];
2551             colorList[i]  = colorList[nrOfSeekAds];
2552             tcList[i] = tcList[nrOfSeekAds];
2553             xList[i]  = xList[nrOfSeekAds];
2554             yList[i]  = yList[nrOfSeekAds];
2555             zList[i]  = zList[nrOfSeekAds];
2556             seekAdList[nrOfSeekAds] = NULL;
2557             break;
2558         }
2559 }
2560
2561 Boolean
2562 MatchSoughtLine(char *line)
2563 {
2564     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2565     int nr, base, inc, u=0; char dummy;
2566
2567     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2568        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569        (u=1) &&
2570        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2571         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2572         // match: compact and save the line
2573         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2574         return TRUE;
2575     }
2576     return FALSE;
2577 }
2578
2579 int
2580 DrawSeekGraph()
2581 {
2582     int i;
2583     if(!seekGraphUp) return FALSE;
2584     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2585     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2586
2587     DrawSeekBackground(0, 0, w, h);
2588     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2589     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2590     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2591         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592         yy = h-1-yy;
2593         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594         if(i%500 == 0) {
2595             char buf[MSG_SIZ];
2596             snprintf(buf, MSG_SIZ, "%d", i);
2597             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598         }
2599     }
2600     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2601     for(i=1; i<100; i+=(i<10?1:5)) {
2602         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2603         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2604         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605             char buf[MSG_SIZ];
2606             snprintf(buf, MSG_SIZ, "%d", i);
2607             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608         }
2609     }
2610     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611     return TRUE;
2612 }
2613
2614 int SeekGraphClick(ClickType click, int x, int y, int moving)
2615 {
2616     static int lastDown = 0, displayed = 0, lastSecond;
2617     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618         if(click == Release || moving) return FALSE;
2619         nrOfSeekAds = 0;
2620         soughtPending = TRUE;
2621         SendToICS(ics_prefix);
2622         SendToICS("sought\n"); // should this be "sought all"?
2623     } else { // issue challenge based on clicked ad
2624         int dist = 10000; int i, closest = 0, second = 0;
2625         for(i=0; i<nrOfSeekAds; i++) {
2626             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2627             if(d < dist) { dist = d; closest = i; }
2628             second += (d - zList[i] < 120); // count in-range ads
2629             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2630         }
2631         if(dist < 120) {
2632             char buf[MSG_SIZ];
2633             second = (second > 1);
2634             if(displayed != closest || second != lastSecond) {
2635                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636                 lastSecond = second; displayed = closest;
2637             }
2638             if(click == Press) {
2639                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640                 lastDown = closest;
2641                 return TRUE;
2642             } // on press 'hit', only show info
2643             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645             SendToICS(ics_prefix);
2646             SendToICS(buf);
2647             return TRUE; // let incoming board of started game pop down the graph
2648         } else if(click == Release) { // release 'miss' is ignored
2649             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650             if(moving == 2) { // right up-click
2651                 nrOfSeekAds = 0; // refresh graph
2652                 soughtPending = TRUE;
2653                 SendToICS(ics_prefix);
2654                 SendToICS("sought\n"); // should this be "sought all"?
2655             }
2656             return TRUE;
2657         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658         // press miss or release hit 'pop down' seek graph
2659         seekGraphUp = FALSE;
2660         DrawPosition(TRUE, NULL);
2661     }
2662     return TRUE;
2663 }
2664
2665 void
2666 read_from_ics(isr, closure, data, count, error)
2667      InputSourceRef isr;
2668      VOIDSTAR closure;
2669      char *data;
2670      int count;
2671      int error;
2672 {
2673 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2674 #define STARTED_NONE 0
2675 #define STARTED_MOVES 1
2676 #define STARTED_BOARD 2
2677 #define STARTED_OBSERVE 3
2678 #define STARTED_HOLDINGS 4
2679 #define STARTED_CHATTER 5
2680 #define STARTED_COMMENT 6
2681 #define STARTED_MOVES_NOHIDE 7
2682
2683     static int started = STARTED_NONE;
2684     static char parse[20000];
2685     static int parse_pos = 0;
2686     static char buf[BUF_SIZE + 1];
2687     static int firstTime = TRUE, intfSet = FALSE;
2688     static ColorClass prevColor = ColorNormal;
2689     static int savingComment = FALSE;
2690     static int cmatch = 0; // continuation sequence match
2691     char *bp;
2692     char str[MSG_SIZ];
2693     int i, oldi;
2694     int buf_len;
2695     int next_out;
2696     int tkind;
2697     int backup;    /* [DM] For zippy color lines */
2698     char *p;
2699     char talker[MSG_SIZ]; // [HGM] chat
2700     int channel;
2701
2702     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2703
2704     if (appData.debugMode) {
2705       if (!error) {
2706         fprintf(debugFP, "<ICS: ");
2707         show_bytes(debugFP, data, count);
2708         fprintf(debugFP, "\n");
2709       }
2710     }
2711
2712     if (appData.debugMode) { int f = forwardMostMove;
2713         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2714                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2715                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2716     }
2717     if (count > 0) {
2718         /* If last read ended with a partial line that we couldn't parse,
2719            prepend it to the new read and try again. */
2720         if (leftover_len > 0) {
2721             for (i=0; i<leftover_len; i++)
2722               buf[i] = buf[leftover_start + i];
2723         }
2724
2725     /* copy new characters into the buffer */
2726     bp = buf + leftover_len;
2727     buf_len=leftover_len;
2728     for (i=0; i<count; i++)
2729     {
2730         // ignore these
2731         if (data[i] == '\r')
2732             continue;
2733
2734         // join lines split by ICS?
2735         if (!appData.noJoin)
2736         {
2737             /*
2738                 Joining just consists of finding matches against the
2739                 continuation sequence, and discarding that sequence
2740                 if found instead of copying it.  So, until a match
2741                 fails, there's nothing to do since it might be the
2742                 complete sequence, and thus, something we don't want
2743                 copied.
2744             */
2745             if (data[i] == cont_seq[cmatch])
2746             {
2747                 cmatch++;
2748                 if (cmatch == strlen(cont_seq))
2749                 {
2750                     cmatch = 0; // complete match.  just reset the counter
2751
2752                     /*
2753                         it's possible for the ICS to not include the space
2754                         at the end of the last word, making our [correct]
2755                         join operation fuse two separate words.  the server
2756                         does this when the space occurs at the width setting.
2757                     */
2758                     if (!buf_len || buf[buf_len-1] != ' ')
2759                     {
2760                         *bp++ = ' ';
2761                         buf_len++;
2762                     }
2763                 }
2764                 continue;
2765             }
2766             else if (cmatch)
2767             {
2768                 /*
2769                     match failed, so we have to copy what matched before
2770                     falling through and copying this character.  In reality,
2771                     this will only ever be just the newline character, but
2772                     it doesn't hurt to be precise.
2773                 */
2774                 strncpy(bp, cont_seq, cmatch);
2775                 bp += cmatch;
2776                 buf_len += cmatch;
2777                 cmatch = 0;
2778             }
2779         }
2780
2781         // copy this char
2782         *bp++ = data[i];
2783         buf_len++;
2784     }
2785
2786         buf[buf_len] = NULLCHAR;
2787 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2788         next_out = 0;
2789         leftover_start = 0;
2790
2791         i = 0;
2792         while (i < buf_len) {
2793             /* Deal with part of the TELNET option negotiation
2794                protocol.  We refuse to do anything beyond the
2795                defaults, except that we allow the WILL ECHO option,
2796                which ICS uses to turn off password echoing when we are
2797                directly connected to it.  We reject this option
2798                if localLineEditing mode is on (always on in xboard)
2799                and we are talking to port 23, which might be a real
2800                telnet server that will try to keep WILL ECHO on permanently.
2801              */
2802             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2803                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2804                 unsigned char option;
2805                 oldi = i;
2806                 switch ((unsigned char) buf[++i]) {
2807                   case TN_WILL:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WILL ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       case TN_ECHO:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "ECHO ");
2814                         /* Reply only if this is a change, according
2815                            to the protocol rules. */
2816                         if (remoteEchoOption) break;
2817                         if (appData.localLineEditing &&
2818                             atoi(appData.icsPort) == TN_PORT) {
2819                             TelnetRequest(TN_DONT, TN_ECHO);
2820                         } else {
2821                             EchoOff();
2822                             TelnetRequest(TN_DO, TN_ECHO);
2823                             remoteEchoOption = TRUE;
2824                         }
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", option);
2829                         /* Whatever this is, we don't want it. */
2830                         TelnetRequest(TN_DONT, option);
2831                         break;
2832                     }
2833                     break;
2834                   case TN_WONT:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<WONT ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       case TN_ECHO:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "ECHO ");
2841                         /* Reply only if this is a change, according
2842                            to the protocol rules. */
2843                         if (!remoteEchoOption) break;
2844                         EchoOn();
2845                         TelnetRequest(TN_DONT, TN_ECHO);
2846                         remoteEchoOption = FALSE;
2847                         break;
2848                       default:
2849                         if (appData.debugMode)
2850                           fprintf(debugFP, "%d ", (unsigned char) option);
2851                         /* Whatever this is, it must already be turned
2852                            off, because we never agree to turn on
2853                            anything non-default, so according to the
2854                            protocol rules, we don't reply. */
2855                         break;
2856                     }
2857                     break;
2858                   case TN_DO:
2859                     if (appData.debugMode)
2860                       fprintf(debugFP, "\n<DO ");
2861                     switch (option = (unsigned char) buf[++i]) {
2862                       default:
2863                         /* Whatever this is, we refuse to do it. */
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         TelnetRequest(TN_WONT, option);
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DONT:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DONT ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         if (appData.debugMode)
2876                           fprintf(debugFP, "%d ", option);
2877                         /* Whatever this is, we are already not doing
2878                            it, because we never agree to do anything
2879                            non-default, so according to the protocol
2880                            rules, we don't reply. */
2881                         break;
2882                     }
2883                     break;
2884                   case TN_IAC:
2885                     if (appData.debugMode)
2886                       fprintf(debugFP, "\n<IAC ");
2887                     /* Doubled IAC; pass it through */
2888                     i--;
2889                     break;
2890                   default:
2891                     if (appData.debugMode)
2892                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2893                     /* Drop all other telnet commands on the floor */
2894                     break;
2895                 }
2896                 if (oldi > next_out)
2897                   SendToPlayer(&buf[next_out], oldi - next_out);
2898                 if (++i > next_out)
2899                   next_out = i;
2900                 continue;
2901             }
2902
2903             /* OK, this at least will *usually* work */
2904             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2905                 loggedOn = TRUE;
2906             }
2907
2908             if (loggedOn && !intfSet) {
2909                 if (ics_type == ICS_ICC) {
2910                   snprintf(str, MSG_SIZ,
2911                           "/set-quietly interface %s\n/set-quietly style 12\n",
2912                           programVersion);
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2915                 } else if (ics_type == ICS_CHESSNET) {
2916                   snprintf(str, MSG_SIZ, "/style 12\n");
2917                 } else {
2918                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2919                   strcat(str, programVersion);
2920                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2921                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2922                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2923 #ifdef WIN32
2924                   strcat(str, "$iset nohighlight 1\n");
2925 #endif
2926                   strcat(str, "$iset lock 1\n$style 12\n");
2927                 }
2928                 SendToICS(str);
2929                 NotifyFrontendLogin();
2930                 intfSet = TRUE;
2931             }
2932
2933             if (started == STARTED_COMMENT) {
2934                 /* Accumulate characters in comment */
2935                 parse[parse_pos++] = buf[i];
2936                 if (buf[i] == '\n') {
2937                     parse[parse_pos] = NULLCHAR;
2938                     if(chattingPartner>=0) {
2939                         char mess[MSG_SIZ];
2940                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2941                         OutputChatMessage(chattingPartner, mess);
2942                         chattingPartner = -1;
2943                         next_out = i+1; // [HGM] suppress printing in ICS window
2944                     } else
2945                     if(!suppressKibitz) // [HGM] kibitz
2946                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2947                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2948                         int nrDigit = 0, nrAlph = 0, j;
2949                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2950                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2951                         parse[parse_pos] = NULLCHAR;
2952                         // try to be smart: if it does not look like search info, it should go to
2953                         // ICS interaction window after all, not to engine-output window.
2954                         for(j=0; j<parse_pos; j++) { // count letters and digits
2955                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2956                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2957                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2958                         }
2959                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2960                             int depth=0; float score;
2961                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2962                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2963                                 pvInfoList[forwardMostMove-1].depth = depth;
2964                                 pvInfoList[forwardMostMove-1].score = 100*score;
2965                             }
2966                             OutputKibitz(suppressKibitz, parse);
2967                         } else {
2968                             char tmp[MSG_SIZ];
2969                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2970                             SendToPlayer(tmp, strlen(tmp));
2971                         }
2972                         next_out = i+1; // [HGM] suppress printing in ICS window
2973                     }
2974                     started = STARTED_NONE;
2975                 } else {
2976                     /* Don't match patterns against characters in comment */
2977                     i++;
2978                     continue;
2979                 }
2980             }
2981             if (started == STARTED_CHATTER) {
2982                 if (buf[i] != '\n') {
2983                     /* Don't match patterns against characters in chatter */
2984                     i++;
2985                     continue;
2986                 }
2987                 started = STARTED_NONE;
2988                 if(suppressKibitz) next_out = i+1;
2989             }
2990
2991             /* Kludge to deal with rcmd protocol */
2992             if (firstTime && looking_at(buf, &i, "\001*")) {
2993                 DisplayFatalError(&buf[1], 0, 1);
2994                 continue;
2995             } else {
2996                 firstTime = FALSE;
2997             }
2998
2999             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000                 ics_type = ICS_ICC;
3001                 ics_prefix = "/";
3002                 if (appData.debugMode)
3003                   fprintf(debugFP, "ics_type %d\n", ics_type);
3004                 continue;
3005             }
3006             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3007                 ics_type = ICS_FICS;
3008                 ics_prefix = "$";
3009                 if (appData.debugMode)
3010                   fprintf(debugFP, "ics_type %d\n", ics_type);
3011                 continue;
3012             }
3013             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3014                 ics_type = ICS_CHESSNET;
3015                 ics_prefix = "/";
3016                 if (appData.debugMode)
3017                   fprintf(debugFP, "ics_type %d\n", ics_type);
3018                 continue;
3019             }
3020
3021             if (!loggedOn &&
3022                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3023                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3024                  looking_at(buf, &i, "will be \"*\""))) {
3025               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3026               continue;
3027             }
3028
3029             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030               char buf[MSG_SIZ];
3031               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3032               DisplayIcsInteractionTitle(buf);
3033               have_set_title = TRUE;
3034             }
3035
3036             /* skip finger notes */
3037             if (started == STARTED_NONE &&
3038                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3039                  (buf[i] == '1' && buf[i+1] == '0')) &&
3040                 buf[i+2] == ':' && buf[i+3] == ' ') {
3041               started = STARTED_CHATTER;
3042               i += 3;
3043               continue;
3044             }
3045
3046             oldi = i;
3047             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3048             if(appData.seekGraph) {
3049                 if(soughtPending && MatchSoughtLine(buf+i)) {
3050                     i = strstr(buf+i, "rated") - buf;
3051                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                     next_out = leftover_start = i;
3053                     started = STARTED_CHATTER;
3054                     suppressKibitz = TRUE;
3055                     continue;
3056                 }
3057                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3058                         && looking_at(buf, &i, "* ads displayed")) {
3059                     soughtPending = FALSE;
3060                     seekGraphUp = TRUE;
3061                     DrawSeekGraph();
3062                     continue;
3063                 }
3064                 if(appData.autoRefresh) {
3065                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3066                         int s = (ics_type == ICS_ICC); // ICC format differs
3067                         if(seekGraphUp)
3068                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3069                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3070                         looking_at(buf, &i, "*% "); // eat prompt
3071                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i; // suppress
3074                         continue;
3075                     }
3076                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3077                         char *p = star_match[0];
3078                         while(*p) {
3079                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3080                             while(*p && *p++ != ' '); // next
3081                         }
3082                         looking_at(buf, &i, "*% "); // eat prompt
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         continue;
3086                     }
3087                 }
3088             }
3089
3090             /* skip formula vars */
3091             if (started == STARTED_NONE &&
3092                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3093               started = STARTED_CHATTER;
3094               i += 3;
3095               continue;
3096             }
3097
3098             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3099             if (appData.autoKibitz && started == STARTED_NONE &&
3100                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3101                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3102                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3103                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3104                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3105                         suppressKibitz = TRUE;
3106                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                         next_out = i;
3108                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3109                                 && (gameMode == IcsPlayingWhite)) ||
3110                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3111                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3112                             started = STARTED_CHATTER; // own kibitz we simply discard
3113                         else {
3114                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3115                             parse_pos = 0; parse[0] = NULLCHAR;
3116                             savingComment = TRUE;
3117                             suppressKibitz = gameMode != IcsObserving ? 2 :
3118                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3119                         }
3120                         continue;
3121                 } else
3122                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3123                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3124                          && atoi(star_match[0])) {
3125                     // suppress the acknowledgements of our own autoKibitz
3126                     char *p;
3127                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3129                     SendToPlayer(star_match[0], strlen(star_match[0]));
3130                     if(looking_at(buf, &i, "*% ")) // eat prompt
3131                         suppressKibitz = FALSE;
3132                     next_out = i;
3133                     continue;
3134                 }
3135             } // [HGM] kibitz: end of patch
3136
3137             // [HGM] chat: intercept tells by users for which we have an open chat window
3138             channel = -1;
3139             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3140                                            looking_at(buf, &i, "* whispers:") ||
3141                                            looking_at(buf, &i, "* kibitzes:") ||
3142                                            looking_at(buf, &i, "* shouts:") ||
3143                                            looking_at(buf, &i, "* c-shouts:") ||
3144                                            looking_at(buf, &i, "--> * ") ||
3145                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3146                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149                 int p;
3150                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3151                 chattingPartner = -1;
3152
3153                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3156                     talker[0] = '['; strcat(talker, "] ");
3157                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3158                     chattingPartner = p; break;
3159                     }
3160                 } else
3161                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3162                 for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("kibitzes", chatPartner[p])) {
3164                         talker[0] = '['; strcat(talker, "] ");
3165                         chattingPartner = p; break;
3166                     }
3167                 } else
3168                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3169                 for(p=0; p<MAX_CHAT; p++) {
3170                     if(!strcmp("whispers", chatPartner[p])) {
3171                         talker[0] = '['; strcat(talker, "] ");
3172                         chattingPartner = p; break;
3173                     }
3174                 } else
3175                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3176                   if(buf[i-8] == '-' && buf[i-3] == 't')
3177                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3178                     if(!strcmp("c-shouts", chatPartner[p])) {
3179                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3180                         chattingPartner = p; break;
3181                     }
3182                   }
3183                   if(chattingPartner < 0)
3184                   for(p=0; p<MAX_CHAT; p++) {
3185                     if(!strcmp("shouts", chatPartner[p])) {
3186                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3187                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3188                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3189                         chattingPartner = p; break;
3190                     }
3191                   }
3192                 }
3193                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3194                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3195                     talker[0] = 0; Colorize(ColorTell, FALSE);
3196                     chattingPartner = p; break;
3197                 }
3198                 if(chattingPartner<0) i = oldi; else {
3199                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3200                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3201                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                     started = STARTED_COMMENT;
3203                     parse_pos = 0; parse[0] = NULLCHAR;
3204                     savingComment = 3 + chattingPartner; // counts as TRUE
3205                     suppressKibitz = TRUE;
3206                     continue;
3207                 }
3208             } // [HGM] chat: end of patch
3209
3210           backup = i;
3211             if (appData.zippyTalk || appData.zippyPlay) {
3212                 /* [DM] Backup address for color zippy lines */
3213 #if ZIPPY
3214                if (loggedOn == TRUE)
3215                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3216                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 #endif
3218             } // [DM] 'else { ' deleted
3219                 if (
3220                     /* Regular tells and says */
3221                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3222                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3223                     looking_at(buf, &i, "* says: ") ||
3224                     /* Don't color "message" or "messages" output */
3225                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3226                     looking_at(buf, &i, "*. * at *:*: ") ||
3227                     looking_at(buf, &i, "--* (*:*): ") ||
3228                     /* Message notifications (same color as tells) */
3229                     looking_at(buf, &i, "* has left a message ") ||
3230                     looking_at(buf, &i, "* just sent you a message:\n") ||
3231                     /* Whispers and kibitzes */
3232                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3233                     looking_at(buf, &i, "* kibitzes: ") ||
3234                     /* Channel tells */
3235                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236
3237                   if (tkind == 1 && strchr(star_match[0], ':')) {
3238                       /* Avoid "tells you:" spoofs in channels */
3239                      tkind = 3;
3240                   }
3241                   if (star_match[0][0] == NULLCHAR ||
3242                       strchr(star_match[0], ' ') ||
3243                       (tkind == 3 && strchr(star_match[1], ' '))) {
3244                     /* Reject bogus matches */
3245                     i = oldi;
3246                   } else {
3247                     if (appData.colorize) {
3248                       if (oldi > next_out) {
3249                         SendToPlayer(&buf[next_out], oldi - next_out);
3250                         next_out = oldi;
3251                       }
3252                       switch (tkind) {
3253                       case 1:
3254                         Colorize(ColorTell, FALSE);
3255                         curColor = ColorTell;
3256                         break;
3257                       case 2:
3258                         Colorize(ColorKibitz, FALSE);
3259                         curColor = ColorKibitz;
3260                         break;
3261                       case 3:
3262                         p = strrchr(star_match[1], '(');
3263                         if (p == NULL) {
3264                           p = star_match[1];
3265                         } else {
3266                           p++;
3267                         }
3268                         if (atoi(p) == 1) {
3269                           Colorize(ColorChannel1, FALSE);
3270                           curColor = ColorChannel1;
3271                         } else {
3272                           Colorize(ColorChannel, FALSE);
3273                           curColor = ColorChannel;
3274                         }
3275                         break;
3276                       case 5:
3277                         curColor = ColorNormal;
3278                         break;
3279                       }
3280                     }
3281                     if (started == STARTED_NONE && appData.autoComment &&
3282                         (gameMode == IcsObserving ||
3283                          gameMode == IcsPlayingWhite ||
3284                          gameMode == IcsPlayingBlack)) {
3285                       parse_pos = i - oldi;
3286                       memcpy(parse, &buf[oldi], parse_pos);
3287                       parse[parse_pos] = NULLCHAR;
3288                       started = STARTED_COMMENT;
3289                       savingComment = TRUE;
3290                     } else {
3291                       started = STARTED_CHATTER;
3292                       savingComment = FALSE;
3293                     }
3294                     loggedOn = TRUE;
3295                     continue;
3296                   }
3297                 }
3298
3299                 if (looking_at(buf, &i, "* s-shouts: ") ||
3300                     looking_at(buf, &i, "* c-shouts: ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorSShout, FALSE);
3307                         curColor = ColorSShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at(buf, &i, "--->")) {
3315                     loggedOn = TRUE;
3316                     continue;
3317                 }
3318
3319                 if (looking_at(buf, &i, "* shouts: ") ||
3320                     looking_at(buf, &i, "--> ")) {
3321                     if (appData.colorize) {
3322                         if (oldi > next_out) {
3323                             SendToPlayer(&buf[next_out], oldi - next_out);
3324                             next_out = oldi;
3325                         }
3326                         Colorize(ColorShout, FALSE);
3327                         curColor = ColorShout;
3328                     }
3329                     loggedOn = TRUE;
3330                     started = STARTED_CHATTER;
3331                     continue;
3332                 }
3333
3334                 if (looking_at( buf, &i, "Challenge:")) {
3335                     if (appData.colorize) {
3336                         if (oldi > next_out) {
3337                             SendToPlayer(&buf[next_out], oldi - next_out);
3338                             next_out = oldi;
3339                         }
3340                         Colorize(ColorChallenge, FALSE);
3341                         curColor = ColorChallenge;
3342                     }
3343                     loggedOn = TRUE;
3344                     continue;
3345                 }
3346
3347                 if (looking_at(buf, &i, "* offers you") ||
3348                     looking_at(buf, &i, "* offers to be") ||
3349                     looking_at(buf, &i, "* would like to") ||
3350                     looking_at(buf, &i, "* requests to") ||
3351                     looking_at(buf, &i, "Your opponent offers") ||
3352                     looking_at(buf, &i, "Your opponent requests")) {
3353
3354                     if (appData.colorize) {
3355                         if (oldi > next_out) {
3356                             SendToPlayer(&buf[next_out], oldi - next_out);
3357                             next_out = oldi;
3358                         }
3359                         Colorize(ColorRequest, FALSE);
3360                         curColor = ColorRequest;
3361                     }
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* (*) seeking")) {
3366                     if (appData.colorize) {
3367                         if (oldi > next_out) {
3368                             SendToPlayer(&buf[next_out], oldi - next_out);
3369                             next_out = oldi;
3370                         }
3371                         Colorize(ColorSeek, FALSE);
3372                         curColor = ColorSeek;
3373                     }
3374                     continue;
3375             }
3376
3377           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378
3379             if (looking_at(buf, &i, "\\   ")) {
3380                 if (prevColor != ColorNormal) {
3381                     if (oldi > next_out) {
3382                         SendToPlayer(&buf[next_out], oldi - next_out);
3383                         next_out = oldi;
3384                     }
3385                     Colorize(prevColor, TRUE);
3386                     curColor = prevColor;
3387                 }
3388                 if (savingComment) {
3389                     parse_pos = i - oldi;
3390                     memcpy(parse, &buf[oldi], parse_pos);
3391                     parse[parse_pos] = NULLCHAR;
3392                     started = STARTED_COMMENT;
3393                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3394                         chattingPartner = savingComment - 3; // kludge to remember the box
3395                 } else {
3396                     started = STARTED_CHATTER;
3397                 }
3398                 continue;
3399             }
3400
3401             if (looking_at(buf, &i, "Black Strength :") ||
3402                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3403                 looking_at(buf, &i, "<10>") ||
3404                 looking_at(buf, &i, "#@#")) {
3405                 /* Wrong board style */
3406                 loggedOn = TRUE;
3407                 SendToICS(ics_prefix);
3408                 SendToICS("set style 12\n");
3409                 SendToICS(ics_prefix);
3410                 SendToICS("refresh\n");
3411                 continue;
3412             }
3413
3414             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415                 ICSInitScript();
3416                 have_sent_ICS_logon = 1;
3417                 continue;
3418             }
3419
3420             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3421                 (looking_at(buf, &i, "\n<12> ") ||
3422                  looking_at(buf, &i, "<12> "))) {
3423                 loggedOn = TRUE;
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_BOARD;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3434                 looking_at(buf, &i, "<b1> ")) {
3435                 if (oldi > next_out) {
3436                     SendToPlayer(&buf[next_out], oldi - next_out);
3437                 }
3438                 next_out = i;
3439                 started = STARTED_HOLDINGS;
3440                 parse_pos = 0;
3441                 continue;
3442             }
3443
3444             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3445                 loggedOn = TRUE;
3446                 /* Header for a move list -- first line */
3447
3448                 switch (ics_getting_history) {
3449                   case H_FALSE:
3450                     switch (gameMode) {
3451                       case IcsIdle:
3452                       case BeginningOfGame:
3453                         /* User typed "moves" or "oldmoves" while we
3454                            were idle.  Pretend we asked for these
3455                            moves and soak them up so user can step
3456                            through them and/or save them.
3457                            */
3458                         Reset(FALSE, TRUE);
3459                         gameMode = IcsObserving;
3460                         ModeHighlight();
3461                         ics_gamenum = -1;
3462                         ics_getting_history = H_GOT_UNREQ_HEADER;
3463                         break;
3464                       case EditGame: /*?*/
3465                       case EditPosition: /*?*/
3466                         /* Should above feature work in these modes too? */
3467                         /* For now it doesn't */
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                       default:
3471                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3472                         break;
3473                     }
3474                     break;
3475                   case H_REQUESTED:
3476                     /* Is this the right one? */
3477                     if (gameInfo.white && gameInfo.black &&
3478                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3479                         strcmp(gameInfo.black, star_match[2]) == 0) {
3480                         /* All is well */
3481                         ics_getting_history = H_GOT_REQ_HEADER;
3482                     }
3483                     break;
3484                   case H_GOT_REQ_HEADER:
3485                   case H_GOT_UNREQ_HEADER:
3486                   case H_GOT_UNWANTED_HEADER:
3487                   case H_GETTING_MOVES:
3488                     /* Should not happen */
3489                     DisplayError(_("Error gathering move list: two headers"), 0);
3490                     ics_getting_history = H_FALSE;
3491                     break;
3492                 }
3493
3494                 /* Save player ratings into gameInfo if needed */
3495                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3496                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3497                     (gameInfo.whiteRating == -1 ||
3498                      gameInfo.blackRating == -1)) {
3499
3500                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3501                     gameInfo.blackRating = string_to_rating(star_match[3]);
3502                     if (appData.debugMode)
3503                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3504                               gameInfo.whiteRating, gameInfo.blackRating);
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i,
3510               "* * match, initial time: * minute*, increment: * second")) {
3511                 /* Header for a move list -- second line */
3512                 /* Initial board will follow if this is a wild game */
3513                 if (gameInfo.event != NULL) free(gameInfo.event);
3514                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3515                 gameInfo.event = StrSave(str);
3516                 /* [HGM] we switched variant. Translate boards if needed. */
3517                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3518                 continue;
3519             }
3520
3521             if (looking_at(buf, &i, "Move  ")) {
3522                 /* Beginning of a move list */
3523                 switch (ics_getting_history) {
3524                   case H_FALSE:
3525                     /* Normally should not happen */
3526                     /* Maybe user hit reset while we were parsing */
3527                     break;
3528                   case H_REQUESTED:
3529                     /* Happens if we are ignoring a move list that is not
3530                      * the one we just requested.  Common if the user
3531                      * tries to observe two games without turning off
3532                      * getMoveList */
3533                     break;
3534                   case H_GETTING_MOVES:
3535                     /* Should not happen */
3536                     DisplayError(_("Error gathering move list: nested"), 0);
3537                     ics_getting_history = H_FALSE;
3538                     break;
3539                   case H_GOT_REQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES;
3542                     parse_pos = 0;
3543                     if (oldi > next_out) {
3544                         SendToPlayer(&buf[next_out], oldi - next_out);
3545                     }
3546                     break;
3547                   case H_GOT_UNREQ_HEADER:
3548                     ics_getting_history = H_GETTING_MOVES;
3549                     started = STARTED_MOVES_NOHIDE;
3550                     parse_pos = 0;
3551                     break;
3552                   case H_GOT_UNWANTED_HEADER:
3553                     ics_getting_history = H_FALSE;
3554                     break;
3555                 }
3556                 continue;
3557             }
3558
3559             if (looking_at(buf, &i, "% ") ||
3560                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3561                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3562                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3563                     soughtPending = FALSE;
3564                     seekGraphUp = TRUE;
3565                     DrawSeekGraph();
3566                 }
3567                 if(suppressKibitz) next_out = i;
3568                 savingComment = FALSE;
3569                 suppressKibitz = 0;
3570                 switch (started) {
3571                   case STARTED_MOVES:
3572                   case STARTED_MOVES_NOHIDE:
3573                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3574                     parse[parse_pos + i - oldi] = NULLCHAR;
3575                     ParseGameHistory(parse);
3576 #if ZIPPY
3577                     if (appData.zippyPlay && first.initDone) {
3578                         FeedMovesToProgram(&first, forwardMostMove);
3579                         if (gameMode == IcsPlayingWhite) {
3580                             if (WhiteOnMove(forwardMostMove)) {
3581                                 if (first.sendTime) {
3582                                   if (first.useColors) {
3583                                     SendToProgram("black\n", &first);
3584                                   }
3585                                   SendTimeRemaining(&first, TRUE);
3586                                 }
3587                                 if (first.useColors) {
3588                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3589                                 }
3590                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3591                                 first.maybeThinking = TRUE;
3592                             } else {
3593                                 if (first.usePlayother) {
3594                                   if (first.sendTime) {
3595                                     SendTimeRemaining(&first, TRUE);
3596                                   }
3597                                   SendToProgram("playother\n", &first);
3598                                   firstMove = FALSE;
3599                                 } else {
3600                                   firstMove = TRUE;
3601                                 }
3602                             }
3603                         } else if (gameMode == IcsPlayingBlack) {
3604                             if (!WhiteOnMove(forwardMostMove)) {
3605                                 if (first.sendTime) {
3606                                   if (first.useColors) {
3607                                     SendToProgram("white\n", &first);
3608                                   }
3609                                   SendTimeRemaining(&first, FALSE);
3610                                 }
3611                                 if (first.useColors) {
3612                                   SendToProgram("black\n", &first);
3613                                 }
3614                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3615                                 first.maybeThinking = TRUE;
3616                             } else {
3617                                 if (first.usePlayother) {
3618                                   if (first.sendTime) {
3619                                     SendTimeRemaining(&first, FALSE);
3620                                   }
3621                                   SendToProgram("playother\n", &first);
3622                                   firstMove = FALSE;
3623                                 } else {
3624                                   firstMove = TRUE;
3625                                 }
3626                             }
3627                         }
3628                     }
3629 #endif
3630                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3631                         /* Moves came from oldmoves or moves command
3632                            while we weren't doing anything else.
3633                            */
3634                         currentMove = forwardMostMove;
3635                         ClearHighlights();/*!!could figure this out*/
3636                         flipView = appData.flipView;
3637                         DrawPosition(TRUE, boards[currentMove]);
3638                         DisplayBothClocks();
3639                         snprintf(str, MSG_SIZ, "%s vs. %s",
3640                                 gameInfo.white, gameInfo.black);
3641                         DisplayTitle(str);
3642                         gameMode = IcsIdle;
3643                     } else {
3644                         /* Moves were history of an active game */
3645                         if (gameInfo.resultDetails != NULL) {
3646                             free(gameInfo.resultDetails);
3647                             gameInfo.resultDetails = NULL;
3648                         }
3649                     }
3650                     HistorySet(parseList, backwardMostMove,
3651                                forwardMostMove, currentMove-1);
3652                     DisplayMove(currentMove - 1);
3653                     if (started == STARTED_MOVES) next_out = i;
3654                     started = STARTED_NONE;
3655                     ics_getting_history = H_FALSE;
3656                     break;
3657
3658                   case STARTED_OBSERVE:
3659                     started = STARTED_NONE;
3660                     SendToICS(ics_prefix);
3661                     SendToICS("refresh\n");
3662                     break;
3663
3664                   default:
3665                     break;
3666                 }
3667                 if(bookHit) { // [HGM] book: simulate book reply
3668                     static char bookMove[MSG_SIZ]; // a bit generous?
3669
3670                     programStats.nodes = programStats.depth = programStats.time =
3671                     programStats.score = programStats.got_only_move = 0;
3672                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3673
3674                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3675                     strcat(bookMove, bookHit);
3676                     HandleMachineMove(bookMove, &first);
3677                 }
3678                 continue;
3679             }
3680
3681             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3682                  started == STARTED_HOLDINGS ||
3683                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3684                 /* Accumulate characters in move list or board */
3685                 parse[parse_pos++] = buf[i];
3686             }
3687
3688             /* Start of game messages.  Mostly we detect start of game
3689                when the first board image arrives.  On some versions
3690                of the ICS, though, we need to do a "refresh" after starting
3691                to observe in order to get the current board right away. */
3692             if (looking_at(buf, &i, "Adding game * to observation list")) {
3693                 started = STARTED_OBSERVE;
3694                 continue;
3695             }
3696
3697             /* Handle auto-observe */
3698             if (appData.autoObserve &&
3699                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3700                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3701                 char *player;
3702                 /* Choose the player that was highlighted, if any. */
3703                 if (star_match[0][0] == '\033' ||
3704                     star_match[1][0] != '\033') {
3705                     player = star_match[0];
3706                 } else {
3707                     player = star_match[2];
3708                 }
3709                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3710                         ics_prefix, StripHighlightAndTitle(player));
3711                 SendToICS(str);
3712
3713                 /* Save ratings from notify string */
3714                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3715                 player1Rating = string_to_rating(star_match[1]);
3716                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3717                 player2Rating = string_to_rating(star_match[3]);
3718
3719                 if (appData.debugMode)
3720                   fprintf(debugFP,
3721                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3722                           player1Name, player1Rating,
3723                           player2Name, player2Rating);
3724
3725                 continue;
3726             }
3727
3728             /* Deal with automatic examine mode after a game,
3729                and with IcsObserving -> IcsExamining transition */
3730             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3731                 looking_at(buf, &i, "has made you an examiner of game *")) {
3732
3733                 int gamenum = atoi(star_match[0]);
3734                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3735                     gamenum == ics_gamenum) {
3736                     /* We were already playing or observing this game;
3737                        no need to refetch history */
3738                     gameMode = IcsExamining;
3739                     if (pausing) {
3740                         pauseExamForwardMostMove = forwardMostMove;
3741                     } else if (currentMove < forwardMostMove) {
3742                         ForwardInner(forwardMostMove);
3743                     }
3744                 } else {
3745                     /* I don't think this case really can happen */
3746                     SendToICS(ics_prefix);
3747                     SendToICS("refresh\n");
3748                 }
3749                 continue;
3750             }
3751
3752             /* Error messages */
3753 //          if (ics_user_moved) {
3754             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3755                 if (looking_at(buf, &i, "Illegal move") ||
3756                     looking_at(buf, &i, "Not a legal move") ||
3757                     looking_at(buf, &i, "Your king is in check") ||
3758                     looking_at(buf, &i, "It isn't your turn") ||
3759                     looking_at(buf, &i, "It is not your move")) {
3760                     /* Illegal move */
3761                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3762                         currentMove = forwardMostMove-1;
3763                         DisplayMove(currentMove - 1); /* before DMError */
3764                         DrawPosition(FALSE, boards[currentMove]);
3765                         SwitchClocks(forwardMostMove-1); // [HGM] race
3766                         DisplayBothClocks();
3767                     }
3768                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3769                     ics_user_moved = 0;
3770                     continue;
3771                 }
3772             }
3773
3774             if (looking_at(buf, &i, "still have time") ||
3775                 looking_at(buf, &i, "not out of time") ||
3776                 looking_at(buf, &i, "either player is out of time") ||
3777                 looking_at(buf, &i, "has timeseal; checking")) {
3778                 /* We must have called his flag a little too soon */
3779                 whiteFlag = blackFlag = FALSE;
3780                 continue;
3781             }
3782
3783             if (looking_at(buf, &i, "added * seconds to") ||
3784                 looking_at(buf, &i, "seconds were added to")) {
3785                 /* Update the clocks */
3786                 SendToICS(ics_prefix);
3787                 SendToICS("refresh\n");
3788                 continue;
3789             }
3790
3791             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3792                 ics_clock_paused = TRUE;
3793                 StopClocks();
3794                 continue;
3795             }
3796
3797             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3798                 ics_clock_paused = FALSE;
3799                 StartClocks();
3800                 continue;
3801             }
3802
3803             /* Grab player ratings from the Creating: message.
3804                Note we have to check for the special case when
3805                the ICS inserts things like [white] or [black]. */
3806             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3807                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3808                 /* star_matches:
3809                    0    player 1 name (not necessarily white)
3810                    1    player 1 rating
3811                    2    empty, white, or black (IGNORED)
3812                    3    player 2 name (not necessarily black)
3813                    4    player 2 rating
3814
3815                    The names/ratings are sorted out when the game
3816                    actually starts (below).
3817                 */
3818                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3819                 player1Rating = string_to_rating(star_match[1]);
3820                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3821                 player2Rating = string_to_rating(star_match[4]);
3822
3823                 if (appData.debugMode)
3824                   fprintf(debugFP,
3825                           "Ratings from 'Creating:' %s %d, %s %d\n",
3826                           player1Name, player1Rating,
3827                           player2Name, player2Rating);
3828
3829                 continue;
3830             }
3831
3832             /* Improved generic start/end-of-game messages */
3833             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3834                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3835                 /* If tkind == 0: */
3836                 /* star_match[0] is the game number */
3837                 /*           [1] is the white player's name */
3838                 /*           [2] is the black player's name */
3839                 /* For end-of-game: */
3840                 /*           [3] is the reason for the game end */
3841                 /*           [4] is a PGN end game-token, preceded by " " */
3842                 /* For start-of-game: */
3843                 /*           [3] begins with "Creating" or "Continuing" */
3844                 /*           [4] is " *" or empty (don't care). */
3845                 int gamenum = atoi(star_match[0]);
3846                 char *whitename, *blackname, *why, *endtoken;
3847                 ChessMove endtype = EndOfFile;
3848
3849                 if (tkind == 0) {
3850                   whitename = star_match[1];
3851                   blackname = star_match[2];
3852                   why = star_match[3];
3853                   endtoken = star_match[4];
3854                 } else {
3855                   whitename = star_match[1];
3856                   blackname = star_match[3];
3857                   why = star_match[5];
3858                   endtoken = star_match[6];
3859                 }
3860
3861                 /* Game start messages */
3862                 if (strncmp(why, "Creating ", 9) == 0 ||
3863                     strncmp(why, "Continuing ", 11) == 0) {
3864                     gs_gamenum = gamenum;
3865                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3866                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3867 #if ZIPPY
3868                     if (appData.zippyPlay) {
3869                         ZippyGameStart(whitename, blackname);
3870                     }
3871 #endif /*ZIPPY*/
3872                     partnerBoardValid = FALSE; // [HGM] bughouse
3873                     continue;
3874                 }
3875
3876                 /* Game end messages */
3877                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3878                     ics_gamenum != gamenum) {
3879                     continue;
3880                 }
3881                 while (endtoken[0] == ' ') endtoken++;
3882                 switch (endtoken[0]) {
3883                   case '*':
3884                   default:
3885                     endtype = GameUnfinished;
3886                     break;
3887                   case '0':
3888                     endtype = BlackWins;
3889                     break;
3890                   case '1':
3891                     if (endtoken[1] == '/')
3892                       endtype = GameIsDrawn;
3893                     else
3894                       endtype = WhiteWins;
3895                     break;
3896                 }
3897                 GameEnds(endtype, why, GE_ICS);
3898 #if ZIPPY
3899                 if (appData.zippyPlay && first.initDone) {
3900                     ZippyGameEnd(endtype, why);
3901                     if (first.pr == NULL) {
3902                       /* Start the next process early so that we'll
3903                          be ready for the next challenge */
3904                       StartChessProgram(&first);
3905                     }
3906                     /* Send "new" early, in case this command takes
3907                        a long time to finish, so that we'll be ready
3908                        for the next challenge. */
3909                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3910                     Reset(TRUE, TRUE);
3911                 }
3912 #endif /*ZIPPY*/
3913                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3914                 continue;
3915             }
3916
3917             if (looking_at(buf, &i, "Removing game * from observation") ||
3918                 looking_at(buf, &i, "no longer observing game *") ||
3919                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3920                 if (gameMode == IcsObserving &&
3921                     atoi(star_match[0]) == ics_gamenum)
3922                   {
3923                       /* icsEngineAnalyze */
3924                       if (appData.icsEngineAnalyze) {
3925                             ExitAnalyzeMode();
3926                             ModeHighlight();
3927                       }
3928                       StopClocks();
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "no longer examining game *")) {
3937                 if (gameMode == IcsExamining &&
3938                     atoi(star_match[0]) == ics_gamenum)
3939                   {
3940                       gameMode = IcsIdle;
3941                       ics_gamenum = -1;
3942                       ics_user_moved = FALSE;
3943                   }
3944                 continue;
3945             }
3946
3947             /* Advance leftover_start past any newlines we find,
3948                so only partial lines can get reparsed */
3949             if (looking_at(buf, &i, "\n")) {
3950                 prevColor = curColor;
3951                 if (curColor != ColorNormal) {
3952                     if (oldi > next_out) {
3953                         SendToPlayer(&buf[next_out], oldi - next_out);
3954                         next_out = oldi;
3955                     }
3956                     Colorize(ColorNormal, FALSE);
3957                     curColor = ColorNormal;
3958                 }
3959                 if (started == STARTED_BOARD) {
3960                     started = STARTED_NONE;
3961                     parse[parse_pos] = NULLCHAR;
3962                     ParseBoard12(parse);
3963                     ics_user_moved = 0;
3964
3965                     /* Send premove here */
3966                     if (appData.premove) {
3967                       char str[MSG_SIZ];
3968                       if (currentMove == 0 &&
3969                           gameMode == IcsPlayingWhite &&
3970                           appData.premoveWhite) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (currentMove == 1 &&
3976                                  gameMode == IcsPlayingBlack &&
3977                                  appData.premoveBlack) {
3978                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3979                         if (appData.debugMode)
3980                           fprintf(debugFP, "Sending premove:\n");
3981                         SendToICS(str);
3982                       } else if (gotPremove) {
3983                         gotPremove = 0;
3984                         ClearPremoveHighlights();
3985                         if (appData.debugMode)
3986                           fprintf(debugFP, "Sending premove:\n");
3987                           UserMoveEvent(premoveFromX, premoveFromY,
3988                                         premoveToX, premoveToY,
3989                                         premovePromoChar);
3990                       }
3991                     }
3992
3993                     /* Usually suppress following prompt */
3994                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3995                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3996                         if (looking_at(buf, &i, "*% ")) {
3997                             savingComment = FALSE;
3998                             suppressKibitz = 0;
3999                         }
4000                     }
4001                     next_out = i;
4002                 } else if (started == STARTED_HOLDINGS) {
4003                     int gamenum;
4004                     char new_piece[MSG_SIZ];
4005                     started = STARTED_NONE;
4006                     parse[parse_pos] = NULLCHAR;
4007                     if (appData.debugMode)
4008                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4009                                                         parse, currentMove);
4010                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4011                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4012                         if (gameInfo.variant == VariantNormal) {
4013                           /* [HGM] We seem to switch variant during a game!
4014                            * Presumably no holdings were displayed, so we have
4015                            * to move the position two files to the right to
4016                            * create room for them!
4017                            */
4018                           VariantClass newVariant;
4019                           switch(gameInfo.boardWidth) { // base guess on board width
4020                                 case 9:  newVariant = VariantShogi; break;
4021                                 case 10: newVariant = VariantGreat; break;
4022                                 default: newVariant = VariantCrazyhouse; break;
4023                           }
4024                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4025                           /* Get a move list just to see the header, which
4026                              will tell us whether this is really bug or zh */
4027                           if (ics_getting_history == H_FALSE) {
4028                             ics_getting_history = H_REQUESTED;
4029                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4030                             SendToICS(str);
4031                           }
4032                         }
4033                         new_piece[0] = NULLCHAR;
4034                         sscanf(parse, "game %d white [%s black [%s <- %s",
4035                                &gamenum, white_holding, black_holding,
4036                                new_piece);
4037                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4038                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4039                         /* [HGM] copy holdings to board holdings area */
4040                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4041                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4042                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4043 #if ZIPPY
4044                         if (appData.zippyPlay && first.initDone) {
4045                             ZippyHoldings(white_holding, black_holding,
4046                                           new_piece);
4047                         }
4048 #endif /*ZIPPY*/
4049                         if (tinyLayout || smallLayout) {
4050                             char wh[16], bh[16];
4051                             PackHolding(wh, white_holding);
4052                             PackHolding(bh, black_holding);
4053                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4054                                     gameInfo.white, gameInfo.black);
4055                         } else {
4056                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4057                                     gameInfo.white, white_holding,
4058                                     gameInfo.black, black_holding);
4059                         }
4060                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4061                         DrawPosition(FALSE, boards[currentMove]);
4062                         DisplayTitle(str);
4063                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4064                         sscanf(parse, "game %d white [%s black [%s <- %s",
4065                                &gamenum, white_holding, black_holding,
4066                                new_piece);
4067                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4068                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4069                         /* [HGM] copy holdings to partner-board holdings area */
4070                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4071                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4072                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4073                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4074                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4075                       }
4076                     }
4077                     /* Suppress following prompt */
4078                     if (looking_at(buf, &i, "*% ")) {
4079                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4080                         savingComment = FALSE;
4081                         suppressKibitz = 0;
4082                     }
4083                     next_out = i;
4084                 }
4085                 continue;
4086             }
4087
4088             i++;                /* skip unparsed character and loop back */
4089         }
4090
4091         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4092 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4093 //          SendToPlayer(&buf[next_out], i - next_out);
4094             started != STARTED_HOLDINGS && leftover_start > next_out) {
4095             SendToPlayer(&buf[next_out], leftover_start - next_out);
4096             next_out = i;
4097         }
4098
4099         leftover_len = buf_len - leftover_start;
4100         /* if buffer ends with something we couldn't parse,
4101            reparse it after appending the next read */
4102
4103     } else if (count == 0) {
4104         RemoveInputSource(isr);
4105         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4106     } else {
4107         DisplayFatalError(_("Error reading from ICS"), error, 1);
4108     }
4109 }
4110
4111
4112 /* Board style 12 looks like this:
4113
4114    <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
4115
4116  * The "<12> " is stripped before it gets to this routine.  The two
4117  * trailing 0's (flip state and clock ticking) are later addition, and
4118  * some chess servers may not have them, or may have only the first.
4119  * Additional trailing fields may be added in the future.
4120  */
4121
4122 #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"
4123
4124 #define RELATION_OBSERVING_PLAYED    0
4125 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4126 #define RELATION_PLAYING_MYMOVE      1
4127 #define RELATION_PLAYING_NOTMYMOVE  -1
4128 #define RELATION_EXAMINING           2
4129 #define RELATION_ISOLATED_BOARD     -3
4130 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4131
4132 void
4133 ParseBoard12(string)
4134      char *string;
4135 {
4136     GameMode newGameMode;
4137     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4138     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4139     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4140     char to_play, board_chars[200];
4141     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4142     char black[32], white[32];
4143     Board board;
4144     int prevMove = currentMove;
4145     int ticking = 2;
4146     ChessMove moveType;
4147     int fromX, fromY, toX, toY;
4148     char promoChar;
4149     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4150     char *bookHit = NULL; // [HGM] book
4151     Boolean weird = FALSE, reqFlag = FALSE;
4152
4153     fromX = fromY = toX = toY = -1;
4154
4155     newGame = FALSE;
4156
4157     if (appData.debugMode)
4158       fprintf(debugFP, _("Parsing board: %s\n"), string);
4159
4160     move_str[0] = NULLCHAR;
4161     elapsed_time[0] = NULLCHAR;
4162     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4163         int  i = 0, j;
4164         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4165             if(string[i] == ' ') { ranks++; files = 0; }
4166             else files++;
4167             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4168             i++;
4169         }
4170         for(j = 0; j <i; j++) board_chars[j] = string[j];
4171         board_chars[i] = '\0';
4172         string += i + 1;
4173     }
4174     n = sscanf(string, PATTERN, &to_play, &double_push,
4175                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4176                &gamenum, white, black, &relation, &basetime, &increment,
4177                &white_stren, &black_stren, &white_time, &black_time,
4178                &moveNum, str, elapsed_time, move_str, &ics_flip,
4179                &ticking);
4180
4181     if (n < 21) {
4182         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4183         DisplayError(str, 0);
4184         return;
4185     }
4186
4187     /* Convert the move number to internal form */
4188     moveNum = (moveNum - 1) * 2;
4189     if (to_play == 'B') moveNum++;
4190     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4191       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4192                         0, 1);
4193       return;
4194     }
4195
4196     switch (relation) {
4197       case RELATION_OBSERVING_PLAYED:
4198       case RELATION_OBSERVING_STATIC:
4199         if (gamenum == -1) {
4200             /* Old ICC buglet */
4201             relation = RELATION_OBSERVING_STATIC;
4202         }
4203         newGameMode = IcsObserving;
4204         break;
4205       case RELATION_PLAYING_MYMOVE:
4206       case RELATION_PLAYING_NOTMYMOVE:
4207         newGameMode =
4208           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4209             IcsPlayingWhite : IcsPlayingBlack;
4210         break;
4211       case RELATION_EXAMINING:
4212         newGameMode = IcsExamining;
4213         break;
4214       case RELATION_ISOLATED_BOARD:
4215       default:
4216         /* Just display this board.  If user was doing something else,
4217            we will forget about it until the next board comes. */
4218         newGameMode = IcsIdle;
4219         break;
4220       case RELATION_STARTING_POSITION:
4221         newGameMode = gameMode;
4222         break;
4223     }
4224
4225     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4226          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4227       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4228       char *toSqr;
4229       for (k = 0; k < ranks; k++) {
4230         for (j = 0; j < files; j++)
4231           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4232         if(gameInfo.holdingsWidth > 1) {
4233              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4234              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4235         }
4236       }
4237       CopyBoard(partnerBoard, board);
4238       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4239         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4240         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4241       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4242       if(toSqr = strchr(str, '-')) {
4243         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4244         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4245       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4246       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4247       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4248       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4249       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4250       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4251                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4252       DisplayMessage(partnerStatus, "");
4253         partnerBoardValid = TRUE;
4254       return;
4255     }
4256
4257     /* Modify behavior for initial board display on move listing
4258        of wild games.
4259        */
4260     switch (ics_getting_history) {
4261       case H_FALSE:
4262       case H_REQUESTED:
4263         break;
4264       case H_GOT_REQ_HEADER:
4265       case H_GOT_UNREQ_HEADER:
4266         /* This is the initial position of the current game */
4267         gamenum = ics_gamenum;
4268         moveNum = 0;            /* old ICS bug workaround */
4269         if (to_play == 'B') {
4270           startedFromSetupPosition = TRUE;
4271           blackPlaysFirst = TRUE;
4272           moveNum = 1;
4273           if (forwardMostMove == 0) forwardMostMove = 1;
4274           if (backwardMostMove == 0) backwardMostMove = 1;
4275           if (currentMove == 0) currentMove = 1;
4276         }
4277         newGameMode = gameMode;
4278         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4279         break;
4280       case H_GOT_UNWANTED_HEADER:
4281         /* This is an initial board that we don't want */
4282         return;
4283       case H_GETTING_MOVES:
4284         /* Should not happen */
4285         DisplayError(_("Error gathering move list: extra board"), 0);
4286         ics_getting_history = H_FALSE;
4287         return;
4288     }
4289
4290    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4291                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4292      /* [HGM] We seem to have switched variant unexpectedly
4293       * Try to guess new variant from board size
4294       */
4295           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4296           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4297           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4298           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4299           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4300           if(!weird) newVariant = VariantNormal;
4301           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4302           /* Get a move list just to see the header, which
4303              will tell us whether this is really bug or zh */
4304           if (ics_getting_history == H_FALSE) {
4305             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4306             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4307             SendToICS(str);
4308           }
4309     }
4310
4311     /* Take action if this is the first board of a new game, or of a
4312        different game than is currently being displayed.  */
4313     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4314         relation == RELATION_ISOLATED_BOARD) {
4315
4316         /* Forget the old game and get the history (if any) of the new one */
4317         if (gameMode != BeginningOfGame) {
4318           Reset(TRUE, TRUE);
4319         }
4320         newGame = TRUE;
4321         if (appData.autoRaiseBoard) BoardToTop();
4322         prevMove = -3;
4323         if (gamenum == -1) {
4324             newGameMode = IcsIdle;
4325         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4326                    appData.getMoveList && !reqFlag) {
4327             /* Need to get game history */
4328             ics_getting_history = H_REQUESTED;
4329             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4330             SendToICS(str);
4331         }
4332
4333         /* Initially flip the board to have black on the bottom if playing
4334            black or if the ICS flip flag is set, but let the user change
4335            it with the Flip View button. */
4336         flipView = appData.autoFlipView ?
4337           (newGameMode == IcsPlayingBlack) || ics_flip :
4338           appData.flipView;
4339
4340         /* Done with values from previous mode; copy in new ones */
4341         gameMode = newGameMode;
4342         ModeHighlight();
4343         ics_gamenum = gamenum;
4344         if (gamenum == gs_gamenum) {
4345             int klen = strlen(gs_kind);
4346             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4347             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4348             gameInfo.event = StrSave(str);
4349         } else {
4350             gameInfo.event = StrSave("ICS game");
4351         }
4352         gameInfo.site = StrSave(appData.icsHost);
4353         gameInfo.date = PGNDate();
4354         gameInfo.round = StrSave("-");
4355         gameInfo.white = StrSave(white);
4356         gameInfo.black = StrSave(black);
4357         timeControl = basetime * 60 * 1000;
4358         timeControl_2 = 0;
4359         timeIncrement = increment * 1000;
4360         movesPerSession = 0;
4361         gameInfo.timeControl = TimeControlTagValue();
4362         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4363   if (appData.debugMode) {
4364     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4365     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4366     setbuf(debugFP, NULL);
4367   }
4368
4369         gameInfo.outOfBook = NULL;
4370
4371         /* Do we have the ratings? */
4372         if (strcmp(player1Name, white) == 0 &&
4373             strcmp(player2Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player1Rating, player2Rating);
4377             gameInfo.whiteRating = player1Rating;
4378             gameInfo.blackRating = player2Rating;
4379         } else if (strcmp(player2Name, white) == 0 &&
4380                    strcmp(player1Name, black) == 0) {
4381             if (appData.debugMode)
4382               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4383                       player2Rating, player1Rating);
4384             gameInfo.whiteRating = player2Rating;
4385             gameInfo.blackRating = player1Rating;
4386         }
4387         player1Name[0] = player2Name[0] = NULLCHAR;
4388
4389         /* Silence shouts if requested */
4390         if (appData.quietPlay &&
4391             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4392             SendToICS(ics_prefix);
4393             SendToICS("set shout 0\n");
4394         }
4395     }
4396
4397     /* Deal with midgame name changes */
4398     if (!newGame) {
4399         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4400             if (gameInfo.white) free(gameInfo.white);
4401             gameInfo.white = StrSave(white);
4402         }
4403         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4404             if (gameInfo.black) free(gameInfo.black);
4405             gameInfo.black = StrSave(black);
4406         }
4407     }
4408
4409     /* Throw away game result if anything actually changes in examine mode */
4410     if (gameMode == IcsExamining && !newGame) {
4411         gameInfo.result = GameUnfinished;
4412         if (gameInfo.resultDetails != NULL) {
4413             free(gameInfo.resultDetails);
4414             gameInfo.resultDetails = NULL;
4415         }
4416     }
4417
4418     /* In pausing && IcsExamining mode, we ignore boards coming
4419        in if they are in a different variation than we are. */
4420     if (pauseExamInvalid) return;
4421     if (pausing && gameMode == IcsExamining) {
4422         if (moveNum <= pauseExamForwardMostMove) {
4423             pauseExamInvalid = TRUE;
4424             forwardMostMove = pauseExamForwardMostMove;
4425             return;
4426         }
4427     }
4428
4429   if (appData.debugMode) {
4430     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4431   }
4432     /* Parse the board */
4433     for (k = 0; k < ranks; k++) {
4434       for (j = 0; j < files; j++)
4435         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4436       if(gameInfo.holdingsWidth > 1) {
4437            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4438            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4439       }
4440     }
4441     CopyBoard(boards[moveNum], board);
4442     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4443     if (moveNum == 0) {
4444         startedFromSetupPosition =
4445           !CompareBoards(board, initialPosition);
4446         if(startedFromSetupPosition)
4447             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4448     }
4449
4450     /* [HGM] Set castling rights. Take the outermost Rooks,
4451        to make it also work for FRC opening positions. Note that board12
4452        is really defective for later FRC positions, as it has no way to
4453        indicate which Rook can castle if they are on the same side of King.
4454        For the initial position we grant rights to the outermost Rooks,
4455        and remember thos rights, and we then copy them on positions
4456        later in an FRC game. This means WB might not recognize castlings with
4457        Rooks that have moved back to their original position as illegal,
4458        but in ICS mode that is not its job anyway.
4459     */
4460     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4461     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4462
4463         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4464             if(board[0][i] == WhiteRook) j = i;
4465         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4467             if(board[0][i] == WhiteRook) j = i;
4468         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4471         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4474         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475
4476         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4477         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4478             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4479         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4480             if(board[BOARD_HEIGHT-1][k] == bKing)
4481                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4482         if(gameInfo.variant == VariantTwoKings) {
4483             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4484             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4485             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4486         }
4487     } else { int r;
4488         r = boards[moveNum][CASTLING][0] = initialRights[0];
4489         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4490         r = boards[moveNum][CASTLING][1] = initialRights[1];
4491         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4492         r = boards[moveNum][CASTLING][3] = initialRights[3];
4493         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4494         r = boards[moveNum][CASTLING][4] = initialRights[4];
4495         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4496         /* wildcastle kludge: always assume King has rights */
4497         r = boards[moveNum][CASTLING][2] = initialRights[2];
4498         r = boards[moveNum][CASTLING][5] = initialRights[5];
4499     }
4500     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4501     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4502
4503
4504     if (ics_getting_history == H_GOT_REQ_HEADER ||
4505         ics_getting_history == H_GOT_UNREQ_HEADER) {
4506         /* This was an initial position from a move list, not
4507            the current position */
4508         return;
4509     }
4510
4511     /* Update currentMove and known move number limits */
4512     newMove = newGame || moveNum > forwardMostMove;
4513
4514     if (newGame) {
4515         forwardMostMove = backwardMostMove = currentMove = moveNum;
4516         if (gameMode == IcsExamining && moveNum == 0) {
4517           /* Workaround for ICS limitation: we are not told the wild
4518              type when starting to examine a game.  But if we ask for
4519              the move list, the move list header will tell us */
4520             ics_getting_history = H_REQUESTED;
4521             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4522             SendToICS(str);
4523         }
4524     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4525                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4526 #if ZIPPY
4527         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4528         /* [HGM] applied this also to an engine that is silently watching        */
4529         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4530             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4531             gameInfo.variant == currentlyInitializedVariant) {
4532           takeback = forwardMostMove - moveNum;
4533           for (i = 0; i < takeback; i++) {
4534             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4535             SendToProgram("undo\n", &first);
4536           }
4537         }
4538 #endif
4539
4540         forwardMostMove = moveNum;
4541         if (!pausing || currentMove > forwardMostMove)
4542           currentMove = forwardMostMove;
4543     } else {
4544         /* New part of history that is not contiguous with old part */
4545         if (pausing && gameMode == IcsExamining) {
4546             pauseExamInvalid = TRUE;
4547             forwardMostMove = pauseExamForwardMostMove;
4548             return;
4549         }
4550         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4551 #if ZIPPY
4552             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4553                 // [HGM] when we will receive the move list we now request, it will be
4554                 // fed to the engine from the first move on. So if the engine is not
4555                 // in the initial position now, bring it there.
4556                 InitChessProgram(&first, 0);
4557             }
4558 #endif
4559             ics_getting_history = H_REQUESTED;
4560             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561             SendToICS(str);
4562         }
4563         forwardMostMove = backwardMostMove = currentMove = moveNum;
4564     }
4565
4566     /* Update the clocks */
4567     if (strchr(elapsed_time, '.')) {
4568       /* Time is in ms */
4569       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4570       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4571     } else {
4572       /* Time is in seconds */
4573       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4574       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4575     }
4576
4577
4578 #if ZIPPY
4579     if (appData.zippyPlay && newGame &&
4580         gameMode != IcsObserving && gameMode != IcsIdle &&
4581         gameMode != IcsExamining)
4582       ZippyFirstBoard(moveNum, basetime, increment);
4583 #endif
4584
4585     /* Put the move on the move list, first converting
4586        to canonical algebraic form. */
4587     if (moveNum > 0) {
4588   if (appData.debugMode) {
4589     if (appData.debugMode) { int f = forwardMostMove;
4590         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4591                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4592                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4593     }
4594     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4595     fprintf(debugFP, "moveNum = %d\n", moveNum);
4596     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4597     setbuf(debugFP, NULL);
4598   }
4599         if (moveNum <= backwardMostMove) {
4600             /* We don't know what the board looked like before
4601                this move.  Punt. */
4602           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             moveList[moveNum - 1][0] = NULLCHAR;
4606         } else if (strcmp(move_str, "none") == 0) {
4607             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4608             /* Again, we don't know what the board looked like;
4609                this is really the start of the game. */
4610             parseList[moveNum - 1][0] = NULLCHAR;
4611             moveList[moveNum - 1][0] = NULLCHAR;
4612             backwardMostMove = moveNum;
4613             startedFromSetupPosition = TRUE;
4614             fromX = fromY = toX = toY = -1;
4615         } else {
4616           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4617           //                 So we parse the long-algebraic move string in stead of the SAN move
4618           int valid; char buf[MSG_SIZ], *prom;
4619
4620           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4621                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4622           // str looks something like "Q/a1-a2"; kill the slash
4623           if(str[1] == '/')
4624             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4625           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4626           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4627                 strcat(buf, prom); // long move lacks promo specification!
4628           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4629                 if(appData.debugMode)
4630                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4631                 safeStrCpy(move_str, buf, MSG_SIZ);
4632           }
4633           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4634                                 &fromX, &fromY, &toX, &toY, &promoChar)
4635                || ParseOneMove(buf, moveNum - 1, &moveType,
4636                                 &fromX, &fromY, &toX, &toY, &promoChar);
4637           // end of long SAN patch
4638           if (valid) {
4639             (void) CoordsToAlgebraic(boards[moveNum - 1],
4640                                      PosFlags(moveNum - 1),
4641                                      fromY, fromX, toY, toX, promoChar,
4642                                      parseList[moveNum-1]);
4643             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4644               case MT_NONE:
4645               case MT_STALEMATE:
4646               default:
4647                 break;
4648               case MT_CHECK:
4649                 if(gameInfo.variant != VariantShogi)
4650                     strcat(parseList[moveNum - 1], "+");
4651                 break;
4652               case MT_CHECKMATE:
4653               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4654                 strcat(parseList[moveNum - 1], "#");
4655                 break;
4656             }
4657             strcat(parseList[moveNum - 1], " ");
4658             strcat(parseList[moveNum - 1], elapsed_time);
4659             /* currentMoveString is set as a side-effect of ParseOneMove */
4660             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4661             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4662             strcat(moveList[moveNum - 1], "\n");
4663
4664             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4665                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4666               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4667                 ChessSquare old, new = boards[moveNum][k][j];
4668                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4669                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4670                   if(old == new) continue;
4671                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4672                   else if(new == WhiteWazir || new == BlackWazir) {
4673                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4674                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4675                       else boards[moveNum][k][j] = old; // preserve type of Gold
4676                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4677                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4678               }
4679           } else {
4680             /* Move from ICS was illegal!?  Punt. */
4681             if (appData.debugMode) {
4682               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4683               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4684             }
4685             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4686             strcat(parseList[moveNum - 1], " ");
4687             strcat(parseList[moveNum - 1], elapsed_time);
4688             moveList[moveNum - 1][0] = NULLCHAR;
4689             fromX = fromY = toX = toY = -1;
4690           }
4691         }
4692   if (appData.debugMode) {
4693     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4694     setbuf(debugFP, NULL);
4695   }
4696
4697 #if ZIPPY
4698         /* Send move to chess program (BEFORE animating it). */
4699         if (appData.zippyPlay && !newGame && newMove &&
4700            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4701
4702             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4703                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4704                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4706                             move_str);
4707                     DisplayError(str, 0);
4708                 } else {
4709                     if (first.sendTime) {
4710                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4711                     }
4712                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4713                     if (firstMove && !bookHit) {
4714                         firstMove = FALSE;
4715                         if (first.useColors) {
4716                           SendToProgram(gameMode == IcsPlayingWhite ?
4717                                         "white\ngo\n" :
4718                                         "black\ngo\n", &first);
4719                         } else {
4720                           SendToProgram("go\n", &first);
4721                         }
4722                         first.maybeThinking = TRUE;
4723                     }
4724                 }
4725             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4726               if (moveList[moveNum - 1][0] == NULLCHAR) {
4727                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4728                 DisplayError(str, 0);
4729               } else {
4730                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4731                 SendMoveToProgram(moveNum - 1, &first);
4732               }
4733             }
4734         }
4735 #endif
4736     }
4737
4738     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4739         /* If move comes from a remote source, animate it.  If it
4740            isn't remote, it will have already been animated. */
4741         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4742             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4743         }
4744         if (!pausing && appData.highlightLastMove) {
4745             SetHighlights(fromX, fromY, toX, toY);
4746         }
4747     }
4748
4749     /* Start the clocks */
4750     whiteFlag = blackFlag = FALSE;
4751     appData.clockMode = !(basetime == 0 && increment == 0);
4752     if (ticking == 0) {
4753       ics_clock_paused = TRUE;
4754       StopClocks();
4755     } else if (ticking == 1) {
4756       ics_clock_paused = FALSE;
4757     }
4758     if (gameMode == IcsIdle ||
4759         relation == RELATION_OBSERVING_STATIC ||
4760         relation == RELATION_EXAMINING ||
4761         ics_clock_paused)
4762       DisplayBothClocks();
4763     else
4764       StartClocks();
4765
4766     /* Display opponents and material strengths */
4767     if (gameInfo.variant != VariantBughouse &&
4768         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4769         if (tinyLayout || smallLayout) {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, (int) gameInfo.variant);
4778         } else {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment);
4783             else
4784               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, VariantName(gameInfo.variant));
4787         }
4788         DisplayTitle(str);
4789   if (appData.debugMode) {
4790     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4791   }
4792     }
4793
4794
4795     /* Display the board */
4796     if (!pausing && !appData.noGUI) {
4797
4798       if (appData.premove)
4799           if (!gotPremove ||
4800              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4801              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4802               ClearPremoveHighlights();
4803
4804       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4805         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4806       DrawPosition(j, boards[currentMove]);
4807
4808       DisplayMove(moveNum - 1);
4809       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4810             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4811               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4812         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4813       }
4814     }
4815
4816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4817 #if ZIPPY
4818     if(bookHit) { // [HGM] book: simulate book reply
4819         static char bookMove[MSG_SIZ]; // a bit generous?
4820
4821         programStats.nodes = programStats.depth = programStats.time =
4822         programStats.score = programStats.got_only_move = 0;
4823         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4824
4825         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4826         strcat(bookMove, bookHit);
4827         HandleMachineMove(bookMove, &first);
4828     }
4829 #endif
4830 }
4831
4832 void
4833 GetMoveListEvent()
4834 {
4835     char buf[MSG_SIZ];
4836     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4837         ics_getting_history = H_REQUESTED;
4838         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4839         SendToICS(buf);
4840     }
4841 }
4842
4843 void
4844 AnalysisPeriodicEvent(force)
4845      int force;
4846 {
4847     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4848          && !force) || !appData.periodicUpdates)
4849       return;
4850
4851     /* Send . command to Crafty to collect stats */
4852     SendToProgram(".\n", &first);
4853
4854     /* Don't send another until we get a response (this makes
4855        us stop sending to old Crafty's which don't understand
4856        the "." command (sending illegal cmds resets node count & time,
4857        which looks bad)) */
4858     programStats.ok_to_send = 0;
4859 }
4860
4861 void ics_update_width(new_width)
4862         int new_width;
4863 {
4864         ics_printf("set width %d\n", new_width);
4865 }
4866
4867 void
4868 SendMoveToProgram(moveNum, cps)
4869      int moveNum;
4870      ChessProgramState *cps;
4871 {
4872     char buf[MSG_SIZ];
4873
4874     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4875         // null move in variant where engine does not understand it (for analysis purposes)
4876         SendBoard(cps, moveNum + 1); // send position after move in stead.
4877         return;
4878     }
4879     if (cps->useUsermove) {
4880       SendToProgram("usermove ", cps);
4881     }
4882     if (cps->useSAN) {
4883       char *space;
4884       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4885         int len = space - parseList[moveNum];
4886         memcpy(buf, parseList[moveNum], len);
4887         buf[len++] = '\n';
4888         buf[len] = NULLCHAR;
4889       } else {
4890         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4891       }
4892       SendToProgram(buf, cps);
4893     } else {
4894       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4895         AlphaRank(moveList[moveNum], 4);
4896         SendToProgram(moveList[moveNum], cps);
4897         AlphaRank(moveList[moveNum], 4); // and back
4898       } else
4899       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4900        * the engine. It would be nice to have a better way to identify castle
4901        * moves here. */
4902       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4903                                                                          && cps->useOOCastle) {
4904         int fromX = moveList[moveNum][0] - AAA;
4905         int fromY = moveList[moveNum][1] - ONE;
4906         int toX = moveList[moveNum][2] - AAA;
4907         int toY = moveList[moveNum][3] - ONE;
4908         if((boards[moveNum][fromY][fromX] == WhiteKing
4909             && boards[moveNum][toY][toX] == WhiteRook)
4910            || (boards[moveNum][fromY][fromX] == BlackKing
4911                && boards[moveNum][toY][toX] == BlackRook)) {
4912           if(toX > fromX) SendToProgram("O-O\n", cps);
4913           else SendToProgram("O-O-O\n", cps);
4914         }
4915         else SendToProgram(moveList[moveNum], cps);
4916       } else
4917       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4918         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4919           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4920           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4921                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4922         } else
4923           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4924                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925         SendToProgram(buf, cps);
4926       }
4927       else SendToProgram(moveList[moveNum], cps);
4928       /* End of additions by Tord */
4929     }
4930
4931     /* [HGM] setting up the opening has brought engine in force mode! */
4932     /*       Send 'go' if we are in a mode where machine should play. */
4933     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4934         (gameMode == TwoMachinesPlay   ||
4935 #if ZIPPY
4936          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4937 #endif
4938          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4939         SendToProgram("go\n", cps);
4940   if (appData.debugMode) {
4941     fprintf(debugFP, "(extra)\n");
4942   }
4943     }
4944     setboardSpoiledMachineBlack = 0;
4945 }
4946
4947 void
4948 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4949      ChessMove moveType;
4950      int fromX, fromY, toX, toY;
4951      char promoChar;
4952 {
4953     char user_move[MSG_SIZ];
4954
4955     switch (moveType) {
4956       default:
4957         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4958                 (int)moveType, fromX, fromY, toX, toY);
4959         DisplayError(user_move + strlen("say "), 0);
4960         break;
4961       case WhiteKingSideCastle:
4962       case BlackKingSideCastle:
4963       case WhiteQueenSideCastleWild:
4964       case BlackQueenSideCastleWild:
4965       /* PUSH Fabien */
4966       case WhiteHSideCastleFR:
4967       case BlackHSideCastleFR:
4968       /* POP Fabien */
4969         snprintf(user_move, MSG_SIZ, "o-o\n");
4970         break;
4971       case WhiteQueenSideCastle:
4972       case BlackQueenSideCastle:
4973       case WhiteKingSideCastleWild:
4974       case BlackKingSideCastleWild:
4975       /* PUSH Fabien */
4976       case WhiteASideCastleFR:
4977       case BlackASideCastleFR:
4978       /* POP Fabien */
4979         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4980         break;
4981       case WhiteNonPromotion:
4982       case BlackNonPromotion:
4983         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4984         break;
4985       case WhitePromotion:
4986       case BlackPromotion:
4987         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4988           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4989                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4990                 PieceToChar(WhiteFerz));
4991         else if(gameInfo.variant == VariantGreat)
4992           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4993                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4994                 PieceToChar(WhiteMan));
4995         else
4996           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4997                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4998                 promoChar);
4999         break;
5000       case WhiteDrop:
5001       case BlackDrop:
5002       drop:
5003         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5004                  ToUpper(PieceToChar((ChessSquare) fromX)),
5005                  AAA + toX, ONE + toY);
5006         break;
5007       case IllegalMove:  /* could be a variant we don't quite understand */
5008         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5009       case NormalMove:
5010       case WhiteCapturesEnPassant:
5011       case BlackCapturesEnPassant:
5012         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5013                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5014         break;
5015     }
5016     SendToICS(user_move);
5017     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5018         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5019 }
5020
5021 void
5022 UploadGameEvent()
5023 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5024     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5025     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5026     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5027         DisplayError("You cannot do this while you are playing or observing", 0);
5028         return;
5029     }
5030     if(gameMode != IcsExamining) { // is this ever not the case?
5031         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5032
5033         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5034           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5035         } else { // on FICS we must first go to general examine mode
5036           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5037         }
5038         if(gameInfo.variant != VariantNormal) {
5039             // try figure out wild number, as xboard names are not always valid on ICS
5040             for(i=1; i<=36; i++) {
5041               snprintf(buf, MSG_SIZ, "wild/%d", i);
5042                 if(StringToVariant(buf) == gameInfo.variant) break;
5043             }
5044             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5045             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5046             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5047         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5048         SendToICS(ics_prefix);
5049         SendToICS(buf);
5050         if(startedFromSetupPosition || backwardMostMove != 0) {
5051           fen = PositionToFEN(backwardMostMove, NULL);
5052           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5053             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5054             SendToICS(buf);
5055           } else { // FICS: everything has to set by separate bsetup commands
5056             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5057             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5058             SendToICS(buf);
5059             if(!WhiteOnMove(backwardMostMove)) {
5060                 SendToICS("bsetup tomove black\n");
5061             }
5062             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5063             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5064             SendToICS(buf);
5065             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5066             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5067             SendToICS(buf);
5068             i = boards[backwardMostMove][EP_STATUS];
5069             if(i >= 0) { // set e.p.
5070               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5071                 SendToICS(buf);
5072             }
5073             bsetup++;
5074           }
5075         }
5076       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5077             SendToICS("bsetup done\n"); // switch to normal examining.
5078     }
5079     for(i = backwardMostMove; i<last; i++) {
5080         char buf[20];
5081         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5082         SendToICS(buf);
5083     }
5084     SendToICS(ics_prefix);
5085     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5086 }
5087
5088 void
5089 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5090      int rf, ff, rt, ft;
5091      char promoChar;
5092      char move[7];
5093 {
5094     if (rf == DROP_RANK) {
5095       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5096       sprintf(move, "%c@%c%c\n",
5097                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5098     } else {
5099         if (promoChar == 'x' || promoChar == NULLCHAR) {
5100           sprintf(move, "%c%c%c%c\n",
5101                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5102         } else {
5103             sprintf(move, "%c%c%c%c%c\n",
5104                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5105         }
5106     }
5107 }
5108
5109 void
5110 ProcessICSInitScript(f)
5111      FILE *f;
5112 {
5113     char buf[MSG_SIZ];
5114
5115     while (fgets(buf, MSG_SIZ, f)) {
5116         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5117     }
5118
5119     fclose(f);
5120 }
5121
5122
5123 static int lastX, lastY, selectFlag, dragging;
5124
5125 void
5126 Sweep(int step)
5127 {
5128     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5129     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5130     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5131     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5132     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5133     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5134     do {
5135         promoSweep -= step;
5136         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5137         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5138         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5139         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5140         if(!step) step = 1;
5141     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5142             appData.testLegality && (promoSweep == king ||
5143             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5144     ChangeDragPiece(promoSweep);
5145 }
5146
5147 int PromoScroll(int x, int y)
5148 {
5149   int step = 0;
5150
5151   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5152   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5153   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5154   if(!step) return FALSE;
5155   lastX = x; lastY = y;
5156   if((promoSweep < BlackPawn) == flipView) step = -step;
5157   if(step > 0) selectFlag = 1;
5158   if(!selectFlag) Sweep(step);
5159   return FALSE;
5160 }
5161
5162 void
5163 NextPiece(int step)
5164 {
5165     ChessSquare piece = boards[currentMove][toY][toX];
5166     do {
5167         pieceSweep -= step;
5168         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5169         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5170         if(!step) step = -1;
5171     } while(PieceToChar(pieceSweep) == '.');
5172     boards[currentMove][toY][toX] = pieceSweep;
5173     DrawPosition(FALSE, boards[currentMove]);
5174     boards[currentMove][toY][toX] = piece;
5175 }
5176 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5177 void
5178 AlphaRank(char *move, int n)
5179 {
5180 //    char *p = move, c; int x, y;
5181
5182     if (appData.debugMode) {
5183         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5184     }
5185
5186     if(move[1]=='*' &&
5187        move[2]>='0' && move[2]<='9' &&
5188        move[3]>='a' && move[3]<='x'    ) {
5189         move[1] = '@';
5190         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5191         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5192     } else
5193     if(move[0]>='0' && move[0]<='9' &&
5194        move[1]>='a' && move[1]<='x' &&
5195        move[2]>='0' && move[2]<='9' &&
5196        move[3]>='a' && move[3]<='x'    ) {
5197         /* input move, Shogi -> normal */
5198         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5199         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5200         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5201         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5202     } else
5203     if(move[1]=='@' &&
5204        move[3]>='0' && move[3]<='9' &&
5205        move[2]>='a' && move[2]<='x'    ) {
5206         move[1] = '*';
5207         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5208         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5209     } else
5210     if(
5211        move[0]>='a' && move[0]<='x' &&
5212        move[3]>='0' && move[3]<='9' &&
5213        move[2]>='a' && move[2]<='x'    ) {
5214          /* output move, normal -> Shogi */
5215         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5216         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5217         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5218         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5219         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5220     }
5221     if (appData.debugMode) {
5222         fprintf(debugFP, "   out = '%s'\n", move);
5223     }
5224 }
5225
5226 char yy_textstr[8000];
5227
5228 /* Parser for moves from gnuchess, ICS, or user typein box */
5229 Boolean
5230 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5231      char *move;
5232      int moveNum;
5233      ChessMove *moveType;
5234      int *fromX, *fromY, *toX, *toY;
5235      char *promoChar;
5236 {
5237     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5238
5239     switch (*moveType) {
5240       case WhitePromotion:
5241       case BlackPromotion:
5242       case WhiteNonPromotion:
5243       case BlackNonPromotion:
5244       case NormalMove:
5245       case WhiteCapturesEnPassant:
5246       case BlackCapturesEnPassant:
5247       case WhiteKingSideCastle:
5248       case WhiteQueenSideCastle:
5249       case BlackKingSideCastle:
5250       case BlackQueenSideCastle:
5251       case WhiteKingSideCastleWild:
5252       case WhiteQueenSideCastleWild:
5253       case BlackKingSideCastleWild:
5254       case BlackQueenSideCastleWild:
5255       /* Code added by Tord: */
5256       case WhiteHSideCastleFR:
5257       case WhiteASideCastleFR:
5258       case BlackHSideCastleFR:
5259       case BlackASideCastleFR:
5260       /* End of code added by Tord */
5261       case IllegalMove:         /* bug or odd chess variant */
5262         *fromX = currentMoveString[0] - AAA;
5263         *fromY = currentMoveString[1] - ONE;
5264         *toX = currentMoveString[2] - AAA;
5265         *toY = currentMoveString[3] - ONE;
5266         *promoChar = currentMoveString[4];
5267         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5268             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5269     if (appData.debugMode) {
5270         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5271     }
5272             *fromX = *fromY = *toX = *toY = 0;
5273             return FALSE;
5274         }
5275         if (appData.testLegality) {
5276           return (*moveType != IllegalMove);
5277         } else {
5278           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5279                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5280         }
5281
5282       case WhiteDrop:
5283       case BlackDrop:
5284         *fromX = *moveType == WhiteDrop ?
5285           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5286           (int) CharToPiece(ToLower(currentMoveString[0]));
5287         *fromY = DROP_RANK;
5288         *toX = currentMoveString[2] - AAA;
5289         *toY = currentMoveString[3] - ONE;
5290         *promoChar = NULLCHAR;
5291         return TRUE;
5292
5293       case AmbiguousMove:
5294       case ImpossibleMove:
5295       case EndOfFile:
5296       case ElapsedTime:
5297       case Comment:
5298       case PGNTag:
5299       case NAG:
5300       case WhiteWins:
5301       case BlackWins:
5302       case GameIsDrawn:
5303       default:
5304     if (appData.debugMode) {
5305         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5306     }
5307         /* bug? */
5308         *fromX = *fromY = *toX = *toY = 0;
5309         *promoChar = NULLCHAR;
5310         return FALSE;
5311     }
5312 }
5313
5314 Boolean pushed = FALSE;
5315 char *lastParseAttempt;
5316
5317 void
5318 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5319 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5320   int fromX, fromY, toX, toY; char promoChar;
5321   ChessMove moveType;
5322   Boolean valid;
5323   int nr = 0;
5324
5325   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5326     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5327     pushed = TRUE;
5328   }
5329   endPV = forwardMostMove;
5330   do {
5331     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5332     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5333     lastParseAttempt = pv;
5334     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5335 if(appData.debugMode){
5336 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);
5337 }
5338     if(!valid && nr == 0 &&
5339        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5340         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5341         // Hande case where played move is different from leading PV move
5342         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5343         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5344         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5345         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5346           endPV += 2; // if position different, keep this
5347           moveList[endPV-1][0] = fromX + AAA;
5348           moveList[endPV-1][1] = fromY + ONE;
5349           moveList[endPV-1][2] = toX + AAA;
5350           moveList[endPV-1][3] = toY + ONE;
5351           parseList[endPV-1][0] = NULLCHAR;
5352           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5353         }
5354       }
5355     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5356     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5357     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5358     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5359         valid++; // allow comments in PV
5360         continue;
5361     }
5362     nr++;
5363     if(endPV+1 > framePtr) break; // no space, truncate
5364     if(!valid) break;
5365     endPV++;
5366     CopyBoard(boards[endPV], boards[endPV-1]);
5367     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5368     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5369     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5370     CoordsToAlgebraic(boards[endPV - 1],
5371                              PosFlags(endPV - 1),
5372                              fromY, fromX, toY, toX, promoChar,
5373                              parseList[endPV - 1]);
5374   } while(valid);
5375   if(atEnd == 2) return; // used hidden, for PV conversion
5376   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5377   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5378   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5379                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5380   DrawPosition(TRUE, boards[currentMove]);
5381 }
5382
5383 int
5384 MultiPV(ChessProgramState *cps)
5385 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5386         int i;
5387         for(i=0; i<cps->nrOptions; i++)
5388             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5389                 return i;
5390         return -1;
5391 }
5392
5393 Boolean
5394 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5395 {
5396         int startPV, multi, lineStart, origIndex = index;
5397         char *p, buf2[MSG_SIZ];
5398
5399         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5400         lastX = x; lastY = y;
5401         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5402         lineStart = startPV = index;
5403         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5404         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5405         index = startPV;
5406         do{ while(buf[index] && buf[index] != '\n') index++;
5407         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5408         buf[index] = 0;
5409         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5410                 int n = first.option[multi].value;
5411                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5412                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5413                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5414                 first.option[multi].value = n;
5415                 *start = *end = 0;
5416                 return FALSE;
5417         }
5418         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5419         *start = startPV; *end = index-1;
5420         return TRUE;
5421 }
5422
5423 char *
5424 PvToSAN(char *pv)
5425 {
5426         static char buf[10*MSG_SIZ];
5427         int i, k=0, savedEnd=endPV;
5428         *buf = NULLCHAR;
5429         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5430         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5431         for(i = forwardMostMove; i<endPV; i++){
5432             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5433             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5434             k += strlen(buf+k);
5435         }
5436         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5437         if(forwardMostMove < savedEnd) PopInner(0);
5438         endPV = savedEnd;
5439         return buf;
5440 }
5441
5442 Boolean
5443 LoadPV(int x, int y)
5444 { // called on right mouse click to load PV
5445   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5446   lastX = x; lastY = y;
5447   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5448   return TRUE;
5449 }
5450
5451 void
5452 UnLoadPV()
5453 {
5454   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5455   if(endPV < 0) return;
5456   endPV = -1;
5457   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5458         Boolean saveAnimate = appData.animate;
5459         if(pushed) {
5460             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5461                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5462             } else storedGames--; // abandon shelved tail of original game
5463         }
5464         pushed = FALSE;
5465         forwardMostMove = currentMove;
5466         currentMove = oldFMM;
5467         appData.animate = FALSE;
5468         ToNrEvent(forwardMostMove);
5469         appData.animate = saveAnimate;
5470   }
5471   currentMove = forwardMostMove;
5472   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5473   ClearPremoveHighlights();
5474   DrawPosition(TRUE, boards[currentMove]);
5475 }
5476
5477 void
5478 MovePV(int x, int y, int h)
5479 { // step through PV based on mouse coordinates (called on mouse move)
5480   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5481
5482   // we must somehow check if right button is still down (might be released off board!)
5483   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5484   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5485   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5486   if(!step) return;
5487   lastX = x; lastY = y;
5488
5489   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5490   if(endPV < 0) return;
5491   if(y < margin) step = 1; else
5492   if(y > h - margin) step = -1;
5493   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5494   currentMove += step;
5495   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5496   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5497                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5498   DrawPosition(FALSE, boards[currentMove]);
5499 }
5500
5501
5502 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5503 // All positions will have equal probability, but the current method will not provide a unique
5504 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5505 #define DARK 1
5506 #define LITE 2
5507 #define ANY 3
5508
5509 int squaresLeft[4];
5510 int piecesLeft[(int)BlackPawn];
5511 int seed, nrOfShuffles;
5512
5513 void GetPositionNumber()
5514 {       // sets global variable seed
5515         int i;
5516
5517         seed = appData.defaultFrcPosition;
5518         if(seed < 0) { // randomize based on time for negative FRC position numbers
5519                 for(i=0; i<50; i++) seed += random();
5520                 seed = random() ^ random() >> 8 ^ random() << 8;
5521                 if(seed<0) seed = -seed;
5522         }
5523 }
5524
5525 int put(Board board, int pieceType, int rank, int n, int shade)
5526 // put the piece on the (n-1)-th empty squares of the given shade
5527 {
5528         int i;
5529
5530         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5531                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5532                         board[rank][i] = (ChessSquare) pieceType;
5533                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5534                         squaresLeft[ANY]--;
5535                         piecesLeft[pieceType]--;
5536                         return i;
5537                 }
5538         }
5539         return -1;
5540 }
5541
5542
5543 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5544 // calculate where the next piece goes, (any empty square), and put it there
5545 {
5546         int i;
5547
5548         i = seed % squaresLeft[shade];
5549         nrOfShuffles *= squaresLeft[shade];
5550         seed /= squaresLeft[shade];
5551         put(board, pieceType, rank, i, shade);
5552 }
5553
5554 void AddTwoPieces(Board board, int pieceType, int rank)
5555 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5556 {
5557         int i, n=squaresLeft[ANY], j=n-1, k;
5558
5559         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5560         i = seed % k;  // pick one
5561         nrOfShuffles *= k;
5562         seed /= k;
5563         while(i >= j) i -= j--;
5564         j = n - 1 - j; i += j;
5565         put(board, pieceType, rank, j, ANY);
5566         put(board, pieceType, rank, i, ANY);
5567 }
5568
5569 void SetUpShuffle(Board board, int number)
5570 {
5571         int i, p, first=1;
5572
5573         GetPositionNumber(); nrOfShuffles = 1;
5574
5575         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5576         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5577         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5578
5579         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5580
5581         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5582             p = (int) board[0][i];
5583             if(p < (int) BlackPawn) piecesLeft[p] ++;
5584             board[0][i] = EmptySquare;
5585         }
5586
5587         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5588             // shuffles restricted to allow normal castling put KRR first
5589             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5590                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5591             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5592                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5593             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5594                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5595             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5596                 put(board, WhiteRook, 0, 0, ANY);
5597             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5598         }
5599
5600         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5601             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5602             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5603                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5604                 while(piecesLeft[p] >= 2) {
5605                     AddOnePiece(board, p, 0, LITE);
5606                     AddOnePiece(board, p, 0, DARK);
5607                 }
5608                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5609             }
5610
5611         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5612             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5613             // but we leave King and Rooks for last, to possibly obey FRC restriction
5614             if(p == (int)WhiteRook) continue;
5615             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5616             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5617         }
5618
5619         // now everything is placed, except perhaps King (Unicorn) and Rooks
5620
5621         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5622             // Last King gets castling rights
5623             while(piecesLeft[(int)WhiteUnicorn]) {
5624                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5625                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5626             }
5627
5628             while(piecesLeft[(int)WhiteKing]) {
5629                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5630                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5631             }
5632
5633
5634         } else {
5635             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5636             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5637         }
5638
5639         // Only Rooks can be left; simply place them all
5640         while(piecesLeft[(int)WhiteRook]) {
5641                 i = put(board, WhiteRook, 0, 0, ANY);
5642                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5643                         if(first) {
5644                                 first=0;
5645                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5646                         }
5647                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5648                 }
5649         }
5650         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5651             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5652         }
5653
5654         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5655 }
5656
5657 int SetCharTable( char *table, const char * map )
5658 /* [HGM] moved here from winboard.c because of its general usefulness */
5659 /*       Basically a safe strcpy that uses the last character as King */
5660 {
5661     int result = FALSE; int NrPieces;
5662
5663     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5664                     && NrPieces >= 12 && !(NrPieces&1)) {
5665         int i; /* [HGM] Accept even length from 12 to 34 */
5666
5667         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5668         for( i=0; i<NrPieces/2-1; i++ ) {
5669             table[i] = map[i];
5670             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5671         }
5672         table[(int) WhiteKing]  = map[NrPieces/2-1];
5673         table[(int) BlackKing]  = map[NrPieces-1];
5674
5675         result = TRUE;
5676     }
5677
5678     return result;
5679 }
5680
5681 void Prelude(Board board)
5682 {       // [HGM] superchess: random selection of exo-pieces
5683         int i, j, k; ChessSquare p;
5684         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5685
5686         GetPositionNumber(); // use FRC position number
5687
5688         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5689             SetCharTable(pieceToChar, appData.pieceToCharTable);
5690             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5691                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5692         }
5693
5694         j = seed%4;                 seed /= 4;
5695         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5696         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5697         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5698         j = seed%3 + (seed%3 >= j); seed /= 3;
5699         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5700         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5701         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5702         j = seed%3;                 seed /= 3;
5703         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5704         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5705         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5706         j = seed%2 + (seed%2 >= j); seed /= 2;
5707         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5708         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5709         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5710         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5711         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5712         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5713         put(board, exoPieces[0],    0, 0, ANY);
5714         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5715 }
5716
5717 void
5718 InitPosition(redraw)
5719      int redraw;
5720 {
5721     ChessSquare (* pieces)[BOARD_FILES];
5722     int i, j, pawnRow, overrule,
5723     oldx = gameInfo.boardWidth,
5724     oldy = gameInfo.boardHeight,
5725     oldh = gameInfo.holdingsWidth;
5726     static int oldv;
5727
5728     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5729
5730     /* [AS] Initialize pv info list [HGM] and game status */
5731     {
5732         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5733             pvInfoList[i].depth = 0;
5734             boards[i][EP_STATUS] = EP_NONE;
5735             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5736         }
5737
5738         initialRulePlies = 0; /* 50-move counter start */
5739
5740         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5741         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5742     }
5743
5744
5745     /* [HGM] logic here is completely changed. In stead of full positions */
5746     /* the initialized data only consist of the two backranks. The switch */
5747     /* selects which one we will use, which is than copied to the Board   */
5748     /* initialPosition, which for the rest is initialized by Pawns and    */
5749     /* empty squares. This initial position is then copied to boards[0],  */
5750     /* possibly after shuffling, so that it remains available.            */
5751
5752     gameInfo.holdingsWidth = 0; /* default board sizes */
5753     gameInfo.boardWidth    = 8;
5754     gameInfo.boardHeight   = 8;
5755     gameInfo.holdingsSize  = 0;
5756     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5757     for(i=0; i<BOARD_FILES-2; i++)
5758       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5759     initialPosition[EP_STATUS] = EP_NONE;
5760     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5761     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5762          SetCharTable(pieceNickName, appData.pieceNickNames);
5763     else SetCharTable(pieceNickName, "............");
5764     pieces = FIDEArray;
5765
5766     switch (gameInfo.variant) {
5767     case VariantFischeRandom:
5768       shuffleOpenings = TRUE;
5769     default:
5770       break;
5771     case VariantShatranj:
5772       pieces = ShatranjArray;
5773       nrCastlingRights = 0;
5774       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5775       break;
5776     case VariantMakruk:
5777       pieces = makrukArray;
5778       nrCastlingRights = 0;
5779       startedFromSetupPosition = TRUE;
5780       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5781       break;
5782     case VariantTwoKings:
5783       pieces = twoKingsArray;
5784       break;
5785     case VariantGrand:
5786       pieces = GrandArray;
5787       nrCastlingRights = 0;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       gameInfo.boardWidth = 10;
5790       gameInfo.boardHeight = 10;
5791       gameInfo.holdingsSize = 7;
5792       break;
5793     case VariantCapaRandom:
5794       shuffleOpenings = TRUE;
5795     case VariantCapablanca:
5796       pieces = CapablancaArray;
5797       gameInfo.boardWidth = 10;
5798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5799       break;
5800     case VariantGothic:
5801       pieces = GothicArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantSChess:
5806       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5807       gameInfo.holdingsSize = 7;
5808       break;
5809     case VariantJanus:
5810       pieces = JanusArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5813       nrCastlingRights = 6;
5814         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5815         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5816         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5817         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5818         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5819         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5820       break;
5821     case VariantFalcon:
5822       pieces = FalconArray;
5823       gameInfo.boardWidth = 10;
5824       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5825       break;
5826     case VariantXiangqi:
5827       pieces = XiangqiArray;
5828       gameInfo.boardWidth  = 9;
5829       gameInfo.boardHeight = 10;
5830       nrCastlingRights = 0;
5831       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5832       break;
5833     case VariantShogi:
5834       pieces = ShogiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 9;
5837       gameInfo.holdingsSize = 7;
5838       nrCastlingRights = 0;
5839       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5840       break;
5841     case VariantCourier:
5842       pieces = CourierArray;
5843       gameInfo.boardWidth  = 12;
5844       nrCastlingRights = 0;
5845       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5846       break;
5847     case VariantKnightmate:
5848       pieces = KnightmateArray;
5849       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5850       break;
5851     case VariantSpartan:
5852       pieces = SpartanArray;
5853       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5854       break;
5855     case VariantFairy:
5856       pieces = fairyArray;
5857       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5858       break;
5859     case VariantGreat:
5860       pieces = GreatArray;
5861       gameInfo.boardWidth = 10;
5862       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5863       gameInfo.holdingsSize = 8;
5864       break;
5865     case VariantSuper:
5866       pieces = FIDEArray;
5867       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5868       gameInfo.holdingsSize = 8;
5869       startedFromSetupPosition = TRUE;
5870       break;
5871     case VariantCrazyhouse:
5872     case VariantBughouse:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5875       gameInfo.holdingsSize = 5;
5876       break;
5877     case VariantWildCastle:
5878       pieces = FIDEArray;
5879       /* !!?shuffle with kings guaranteed to be on d or e file */
5880       shuffleOpenings = 1;
5881       break;
5882     case VariantNoCastle:
5883       pieces = FIDEArray;
5884       nrCastlingRights = 0;
5885       /* !!?unconstrained back-rank shuffle */
5886       shuffleOpenings = 1;
5887       break;
5888     }
5889
5890     overrule = 0;
5891     if(appData.NrFiles >= 0) {
5892         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5893         gameInfo.boardWidth = appData.NrFiles;
5894     }
5895     if(appData.NrRanks >= 0) {
5896         gameInfo.boardHeight = appData.NrRanks;
5897     }
5898     if(appData.holdingsSize >= 0) {
5899         i = appData.holdingsSize;
5900         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5901         gameInfo.holdingsSize = i;
5902     }
5903     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5904     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5905         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5906
5907     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5908     if(pawnRow < 1) pawnRow = 1;
5909     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5910
5911     /* User pieceToChar list overrules defaults */
5912     if(appData.pieceToCharTable != NULL)
5913         SetCharTable(pieceToChar, appData.pieceToCharTable);
5914
5915     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5916
5917         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5918             s = (ChessSquare) 0; /* account holding counts in guard band */
5919         for( i=0; i<BOARD_HEIGHT; i++ )
5920             initialPosition[i][j] = s;
5921
5922         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5923         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5924         initialPosition[pawnRow][j] = WhitePawn;
5925         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5926         if(gameInfo.variant == VariantXiangqi) {
5927             if(j&1) {
5928                 initialPosition[pawnRow][j] =
5929                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5930                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5931                    initialPosition[2][j] = WhiteCannon;
5932                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5933                 }
5934             }
5935         }
5936         if(gameInfo.variant == VariantGrand) {
5937             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5938                initialPosition[0][j] = WhiteRook;
5939                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5940             }
5941         }
5942         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5943     }
5944     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5945
5946             j=BOARD_LEFT+1;
5947             initialPosition[1][j] = WhiteBishop;
5948             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5949             j=BOARD_RGHT-2;
5950             initialPosition[1][j] = WhiteRook;
5951             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5952     }
5953
5954     if( nrCastlingRights == -1) {
5955         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5956         /*       This sets default castling rights from none to normal corners   */
5957         /* Variants with other castling rights must set them themselves above    */
5958         nrCastlingRights = 6;
5959
5960         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5961         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5962         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5963         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5964         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5965         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5966      }
5967
5968      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5969      if(gameInfo.variant == VariantGreat) { // promotion commoners
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5974      }
5975      if( gameInfo.variant == VariantSChess ) {
5976       initialPosition[1][0] = BlackMarshall;
5977       initialPosition[2][0] = BlackAngel;
5978       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5979       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5980       initialPosition[1][1] = initialPosition[2][1] = 
5981       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5982      }
5983   if (appData.debugMode) {
5984     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5985   }
5986     if(shuffleOpenings) {
5987         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5988         startedFromSetupPosition = TRUE;
5989     }
5990     if(startedFromPositionFile) {
5991       /* [HGM] loadPos: use PositionFile for every new game */
5992       CopyBoard(initialPosition, filePosition);
5993       for(i=0; i<nrCastlingRights; i++)
5994           initialRights[i] = filePosition[CASTLING][i];
5995       startedFromSetupPosition = TRUE;
5996     }
5997
5998     CopyBoard(boards[0], initialPosition);
5999
6000     if(oldx != gameInfo.boardWidth ||
6001        oldy != gameInfo.boardHeight ||
6002        oldv != gameInfo.variant ||
6003        oldh != gameInfo.holdingsWidth
6004                                          )
6005             InitDrawingSizes(-2 ,0);
6006
6007     oldv = gameInfo.variant;
6008     if (redraw)
6009       DrawPosition(TRUE, boards[currentMove]);
6010 }
6011
6012 void
6013 SendBoard(cps, moveNum)
6014      ChessProgramState *cps;
6015      int moveNum;
6016 {
6017     char message[MSG_SIZ];
6018
6019     if (cps->useSetboard) {
6020       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6021       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6022       SendToProgram(message, cps);
6023       free(fen);
6024
6025     } else {
6026       ChessSquare *bp;
6027       int i, j;
6028       /* Kludge to set black to move, avoiding the troublesome and now
6029        * deprecated "black" command.
6030        */
6031       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6032         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6033
6034       SendToProgram("edit\n", cps);
6035       SendToProgram("#\n", cps);
6036       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6037         bp = &boards[moveNum][i][BOARD_LEFT];
6038         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6039           if ((int) *bp < (int) BlackPawn) {
6040             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6041                     AAA + j, ONE + i);
6042             if(message[0] == '+' || message[0] == '~') {
6043               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6044                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6045                         AAA + j, ONE + i);
6046             }
6047             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6048                 message[1] = BOARD_RGHT   - 1 - j + '1';
6049                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6050             }
6051             SendToProgram(message, cps);
6052           }
6053         }
6054       }
6055
6056       SendToProgram("c\n", cps);
6057       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6058         bp = &boards[moveNum][i][BOARD_LEFT];
6059         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6060           if (((int) *bp != (int) EmptySquare)
6061               && ((int) *bp >= (int) BlackPawn)) {
6062             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6063                     AAA + j, ONE + i);
6064             if(message[0] == '+' || message[0] == '~') {
6065               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6066                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6067                         AAA + j, ONE + i);
6068             }
6069             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6070                 message[1] = BOARD_RGHT   - 1 - j + '1';
6071                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6072             }
6073             SendToProgram(message, cps);
6074           }
6075         }
6076       }
6077
6078       SendToProgram(".\n", cps);
6079     }
6080     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6081 }
6082
6083 ChessSquare
6084 DefaultPromoChoice(int white)
6085 {
6086     ChessSquare result;
6087     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6088         result = WhiteFerz; // no choice
6089     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6090         result= WhiteKing; // in Suicide Q is the last thing we want
6091     else if(gameInfo.variant == VariantSpartan)
6092         result = white ? WhiteQueen : WhiteAngel;
6093     else result = WhiteQueen;
6094     if(!white) result = WHITE_TO_BLACK result;
6095     return result;
6096 }
6097
6098 static int autoQueen; // [HGM] oneclick
6099
6100 int
6101 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6102 {
6103     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6104     /* [HGM] add Shogi promotions */
6105     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6106     ChessSquare piece;
6107     ChessMove moveType;
6108     Boolean premove;
6109
6110     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6111     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6112
6113     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6114       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6115         return FALSE;
6116
6117     piece = boards[currentMove][fromY][fromX];
6118     if(gameInfo.variant == VariantShogi) {
6119         promotionZoneSize = BOARD_HEIGHT/3;
6120         highestPromotingPiece = (int)WhiteFerz;
6121     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6122         promotionZoneSize = 3;
6123     }
6124
6125     // Treat Lance as Pawn when it is not representing Amazon
6126     if(gameInfo.variant != VariantSuper) {
6127         if(piece == WhiteLance) piece = WhitePawn; else
6128         if(piece == BlackLance) piece = BlackPawn;
6129     }
6130
6131     // next weed out all moves that do not touch the promotion zone at all
6132     if((int)piece >= BlackPawn) {
6133         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6134              return FALSE;
6135         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6136     } else {
6137         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6138            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6139     }
6140
6141     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6142
6143     // weed out mandatory Shogi promotions
6144     if(gameInfo.variant == VariantShogi) {
6145         if(piece >= BlackPawn) {
6146             if(toY == 0 && piece == BlackPawn ||
6147                toY == 0 && piece == BlackQueen ||
6148                toY <= 1 && piece == BlackKnight) {
6149                 *promoChoice = '+';
6150                 return FALSE;
6151             }
6152         } else {
6153             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6154                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6155                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6156                 *promoChoice = '+';
6157                 return FALSE;
6158             }
6159         }
6160     }
6161
6162     // weed out obviously illegal Pawn moves
6163     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6164         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6165         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6166         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6167         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6168         // note we are not allowed to test for valid (non-)capture, due to premove
6169     }
6170
6171     // we either have a choice what to promote to, or (in Shogi) whether to promote
6172     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6173         *promoChoice = PieceToChar(BlackFerz);  // no choice
6174         return FALSE;
6175     }
6176     // no sense asking what we must promote to if it is going to explode...
6177     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6178         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6179         return FALSE;
6180     }
6181     // give caller the default choice even if we will not make it
6182     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6183     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6184     if(        sweepSelect && gameInfo.variant != VariantGreat
6185                            && gameInfo.variant != VariantGrand
6186                            && gameInfo.variant != VariantSuper) return FALSE;
6187     if(autoQueen) return FALSE; // predetermined
6188
6189     // suppress promotion popup on illegal moves that are not premoves
6190     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6191               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6192     if(appData.testLegality && !premove) {
6193         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6194                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6195         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6196             return FALSE;
6197     }
6198
6199     return TRUE;
6200 }
6201
6202 int
6203 InPalace(row, column)
6204      int row, column;
6205 {   /* [HGM] for Xiangqi */
6206     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6207          column < (BOARD_WIDTH + 4)/2 &&
6208          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6209     return FALSE;
6210 }
6211
6212 int
6213 PieceForSquare (x, y)
6214      int x;
6215      int y;
6216 {
6217   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6218      return -1;
6219   else
6220      return boards[currentMove][y][x];
6221 }
6222
6223 int
6224 OKToStartUserMove(x, y)
6225      int x, y;
6226 {
6227     ChessSquare from_piece;
6228     int white_piece;
6229
6230     if (matchMode) return FALSE;
6231     if (gameMode == EditPosition) return TRUE;
6232
6233     if (x >= 0 && y >= 0)
6234       from_piece = boards[currentMove][y][x];
6235     else
6236       from_piece = EmptySquare;
6237
6238     if (from_piece == EmptySquare) return FALSE;
6239
6240     white_piece = (int)from_piece >= (int)WhitePawn &&
6241       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6242
6243     switch (gameMode) {
6244       case AnalyzeFile:
6245       case TwoMachinesPlay:
6246       case EndOfGame:
6247         return FALSE;
6248
6249       case IcsObserving:
6250       case IcsIdle:
6251         return FALSE;
6252
6253       case MachinePlaysWhite:
6254       case IcsPlayingBlack:
6255         if (appData.zippyPlay) return FALSE;
6256         if (white_piece) {
6257             DisplayMoveError(_("You are playing Black"));
6258             return FALSE;
6259         }
6260         break;
6261
6262       case MachinePlaysBlack:
6263       case IcsPlayingWhite:
6264         if (appData.zippyPlay) return FALSE;
6265         if (!white_piece) {
6266             DisplayMoveError(_("You are playing White"));
6267             return FALSE;
6268         }
6269         break;
6270
6271       case PlayFromGameFile:
6272             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6273       case EditGame:
6274         if (!white_piece && WhiteOnMove(currentMove)) {
6275             DisplayMoveError(_("It is White's turn"));
6276             return FALSE;
6277         }
6278         if (white_piece && !WhiteOnMove(currentMove)) {
6279             DisplayMoveError(_("It is Black's turn"));
6280             return FALSE;
6281         }
6282         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6283             /* Editing correspondence game history */
6284             /* Could disallow this or prompt for confirmation */
6285             cmailOldMove = -1;
6286         }
6287         break;
6288
6289       case BeginningOfGame:
6290         if (appData.icsActive) return FALSE;
6291         if (!appData.noChessProgram) {
6292             if (!white_piece) {
6293                 DisplayMoveError(_("You are playing White"));
6294                 return FALSE;
6295             }
6296         }
6297         break;
6298
6299       case Training:
6300         if (!white_piece && WhiteOnMove(currentMove)) {
6301             DisplayMoveError(_("It is White's turn"));
6302             return FALSE;
6303         }
6304         if (white_piece && !WhiteOnMove(currentMove)) {
6305             DisplayMoveError(_("It is Black's turn"));
6306             return FALSE;
6307         }
6308         break;
6309
6310       default:
6311       case IcsExamining:
6312         break;
6313     }
6314     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6315         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6316         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6317         && gameMode != AnalyzeFile && gameMode != Training) {
6318         DisplayMoveError(_("Displayed position is not current"));
6319         return FALSE;
6320     }
6321     return TRUE;
6322 }
6323
6324 Boolean
6325 OnlyMove(int *x, int *y, Boolean captures) {
6326     DisambiguateClosure cl;
6327     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6328     switch(gameMode) {
6329       case MachinePlaysBlack:
6330       case IcsPlayingWhite:
6331       case BeginningOfGame:
6332         if(!WhiteOnMove(currentMove)) return FALSE;
6333         break;
6334       case MachinePlaysWhite:
6335       case IcsPlayingBlack:
6336         if(WhiteOnMove(currentMove)) return FALSE;
6337         break;
6338       case EditGame:
6339         break;
6340       default:
6341         return FALSE;
6342     }
6343     cl.pieceIn = EmptySquare;
6344     cl.rfIn = *y;
6345     cl.ffIn = *x;
6346     cl.rtIn = -1;
6347     cl.ftIn = -1;
6348     cl.promoCharIn = NULLCHAR;
6349     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6350     if( cl.kind == NormalMove ||
6351         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6352         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6353         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6354       fromX = cl.ff;
6355       fromY = cl.rf;
6356       *x = cl.ft;
6357       *y = cl.rt;
6358       return TRUE;
6359     }
6360     if(cl.kind != ImpossibleMove) return FALSE;
6361     cl.pieceIn = EmptySquare;
6362     cl.rfIn = -1;
6363     cl.ffIn = -1;
6364     cl.rtIn = *y;
6365     cl.ftIn = *x;
6366     cl.promoCharIn = NULLCHAR;
6367     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6368     if( cl.kind == NormalMove ||
6369         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6370         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6371         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6372       fromX = cl.ff;
6373       fromY = cl.rf;
6374       *x = cl.ft;
6375       *y = cl.rt;
6376       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6377       return TRUE;
6378     }
6379     return FALSE;
6380 }
6381
6382 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6383 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6384 int lastLoadGameUseList = FALSE;
6385 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6386 ChessMove lastLoadGameStart = EndOfFile;
6387
6388 void
6389 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6390      int fromX, fromY, toX, toY;
6391      int promoChar;
6392 {
6393     ChessMove moveType;
6394     ChessSquare pdown, pup;
6395
6396     /* Check if the user is playing in turn.  This is complicated because we
6397        let the user "pick up" a piece before it is his turn.  So the piece he
6398        tried to pick up may have been captured by the time he puts it down!
6399        Therefore we use the color the user is supposed to be playing in this
6400        test, not the color of the piece that is currently on the starting
6401        square---except in EditGame mode, where the user is playing both
6402        sides; fortunately there the capture race can't happen.  (It can
6403        now happen in IcsExamining mode, but that's just too bad.  The user
6404        will get a somewhat confusing message in that case.)
6405        */
6406
6407     switch (gameMode) {
6408       case AnalyzeFile:
6409       case TwoMachinesPlay:
6410       case EndOfGame:
6411       case IcsObserving:
6412       case IcsIdle:
6413         /* We switched into a game mode where moves are not accepted,
6414            perhaps while the mouse button was down. */
6415         return;
6416
6417       case MachinePlaysWhite:
6418         /* User is moving for Black */
6419         if (WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return;
6422         }
6423         break;
6424
6425       case MachinePlaysBlack:
6426         /* User is moving for White */
6427         if (!WhiteOnMove(currentMove)) {
6428             DisplayMoveError(_("It is Black's turn"));
6429             return;
6430         }
6431         break;
6432
6433       case PlayFromGameFile:
6434             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6435       case EditGame:
6436       case IcsExamining:
6437       case BeginningOfGame:
6438       case AnalyzeMode:
6439       case Training:
6440         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6441         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6442             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6443             /* User is moving for Black */
6444             if (WhiteOnMove(currentMove)) {
6445                 DisplayMoveError(_("It is White's turn"));
6446                 return;
6447             }
6448         } else {
6449             /* User is moving for White */
6450             if (!WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is Black's turn"));
6452                 return;
6453             }
6454         }
6455         break;
6456
6457       case IcsPlayingBlack:
6458         /* User is moving for Black */
6459         if (WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is White's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       case IcsPlayingWhite:
6479         /* User is moving for White */
6480         if (!WhiteOnMove(currentMove)) {
6481             if (!appData.premove) {
6482                 DisplayMoveError(_("It is Black's turn"));
6483             } else if (toX >= 0 && toY >= 0) {
6484                 premoveToX = toX;
6485                 premoveToY = toY;
6486                 premoveFromX = fromX;
6487                 premoveFromY = fromY;
6488                 premovePromoChar = promoChar;
6489                 gotPremove = 1;
6490                 if (appData.debugMode)
6491                     fprintf(debugFP, "Got premove: fromX %d,"
6492                             "fromY %d, toX %d, toY %d\n",
6493                             fromX, fromY, toX, toY);
6494             }
6495             return;
6496         }
6497         break;
6498
6499       default:
6500         break;
6501
6502       case EditPosition:
6503         /* EditPosition, empty square, or different color piece;
6504            click-click move is possible */
6505         if (toX == -2 || toY == -2) {
6506             boards[0][fromY][fromX] = EmptySquare;
6507             DrawPosition(FALSE, boards[currentMove]);
6508             return;
6509         } else if (toX >= 0 && toY >= 0) {
6510             boards[0][toY][toX] = boards[0][fromY][fromX];
6511             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6512                 if(boards[0][fromY][0] != EmptySquare) {
6513                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6514                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6515                 }
6516             } else
6517             if(fromX == BOARD_RGHT+1) {
6518                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6519                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6520                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6521                 }
6522             } else
6523             boards[0][fromY][fromX] = EmptySquare;
6524             DrawPosition(FALSE, boards[currentMove]);
6525             return;
6526         }
6527         return;
6528     }
6529
6530     if(toX < 0 || toY < 0) return;
6531     pdown = boards[currentMove][fromY][fromX];
6532     pup = boards[currentMove][toY][toX];
6533
6534     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6535     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6536          if( pup != EmptySquare ) return;
6537          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6538            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6539                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6540            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6541            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6542            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6543            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6544          fromY = DROP_RANK;
6545     }
6546
6547     /* [HGM] always test for legality, to get promotion info */
6548     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6549                                          fromY, fromX, toY, toX, promoChar);
6550
6551     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6552
6553     /* [HGM] but possibly ignore an IllegalMove result */
6554     if (appData.testLegality) {
6555         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6556             DisplayMoveError(_("Illegal move"));
6557             return;
6558         }
6559     }
6560
6561     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6562 }
6563
6564 /* Common tail of UserMoveEvent and DropMenuEvent */
6565 int
6566 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6567      ChessMove moveType;
6568      int fromX, fromY, toX, toY;
6569      /*char*/int promoChar;
6570 {
6571     char *bookHit = 0;
6572
6573     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6574         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6575         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6576         if(WhiteOnMove(currentMove)) {
6577             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6578         } else {
6579             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6580         }
6581     }
6582
6583     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6584        move type in caller when we know the move is a legal promotion */
6585     if(moveType == NormalMove && promoChar)
6586         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6587
6588     /* [HGM] <popupFix> The following if has been moved here from
6589        UserMoveEvent(). Because it seemed to belong here (why not allow
6590        piece drops in training games?), and because it can only be
6591        performed after it is known to what we promote. */
6592     if (gameMode == Training) {
6593       /* compare the move played on the board to the next move in the
6594        * game. If they match, display the move and the opponent's response.
6595        * If they don't match, display an error message.
6596        */
6597       int saveAnimate;
6598       Board testBoard;
6599       CopyBoard(testBoard, boards[currentMove]);
6600       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6601
6602       if (CompareBoards(testBoard, boards[currentMove+1])) {
6603         ForwardInner(currentMove+1);
6604
6605         /* Autoplay the opponent's response.
6606          * if appData.animate was TRUE when Training mode was entered,
6607          * the response will be animated.
6608          */
6609         saveAnimate = appData.animate;
6610         appData.animate = animateTraining;
6611         ForwardInner(currentMove+1);
6612         appData.animate = saveAnimate;
6613
6614         /* check for the end of the game */
6615         if (currentMove >= forwardMostMove) {
6616           gameMode = PlayFromGameFile;
6617           ModeHighlight();
6618           SetTrainingModeOff();
6619           DisplayInformation(_("End of game"));
6620         }
6621       } else {
6622         DisplayError(_("Incorrect move"), 0);
6623       }
6624       return 1;
6625     }
6626
6627   /* Ok, now we know that the move is good, so we can kill
6628      the previous line in Analysis Mode */
6629   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6630                                 && currentMove < forwardMostMove) {
6631     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6632     else forwardMostMove = currentMove;
6633   }
6634
6635   /* If we need the chess program but it's dead, restart it */
6636   ResurrectChessProgram();
6637
6638   /* A user move restarts a paused game*/
6639   if (pausing)
6640     PauseEvent();
6641
6642   thinkOutput[0] = NULLCHAR;
6643
6644   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6645
6646   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6647     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6648     return 1;
6649   }
6650
6651   if (gameMode == BeginningOfGame) {
6652     if (appData.noChessProgram) {
6653       gameMode = EditGame;
6654       SetGameInfo();
6655     } else {
6656       char buf[MSG_SIZ];
6657       gameMode = MachinePlaysBlack;
6658       StartClocks();
6659       SetGameInfo();
6660       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6661       DisplayTitle(buf);
6662       if (first.sendName) {
6663         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6664         SendToProgram(buf, &first);
6665       }
6666       StartClocks();
6667     }
6668     ModeHighlight();
6669   }
6670
6671   /* Relay move to ICS or chess engine */
6672   if (appData.icsActive) {
6673     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6674         gameMode == IcsExamining) {
6675       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6676         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6677         SendToICS("draw ");
6678         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6679       }
6680       // also send plain move, in case ICS does not understand atomic claims
6681       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6682       ics_user_moved = 1;
6683     }
6684   } else {
6685     if (first.sendTime && (gameMode == BeginningOfGame ||
6686                            gameMode == MachinePlaysWhite ||
6687                            gameMode == MachinePlaysBlack)) {
6688       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6689     }
6690     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6691          // [HGM] book: if program might be playing, let it use book
6692         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6693         first.maybeThinking = TRUE;
6694     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6695         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6696         SendBoard(&first, currentMove+1);
6697     } else SendMoveToProgram(forwardMostMove-1, &first);
6698     if (currentMove == cmailOldMove + 1) {
6699       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6700     }
6701   }
6702
6703   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6704
6705   switch (gameMode) {
6706   case EditGame:
6707     if(appData.testLegality)
6708     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6709     case MT_NONE:
6710     case MT_CHECK:
6711       break;
6712     case MT_CHECKMATE:
6713     case MT_STAINMATE:
6714       if (WhiteOnMove(currentMove)) {
6715         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6716       } else {
6717         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6718       }
6719       break;
6720     case MT_STALEMATE:
6721       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6722       break;
6723     }
6724     break;
6725
6726   case MachinePlaysBlack:
6727   case MachinePlaysWhite:
6728     /* disable certain menu options while machine is thinking */
6729     SetMachineThinkingEnables();
6730     break;
6731
6732   default:
6733     break;
6734   }
6735
6736   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6737   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6738
6739   if(bookHit) { // [HGM] book: simulate book reply
6740         static char bookMove[MSG_SIZ]; // a bit generous?
6741
6742         programStats.nodes = programStats.depth = programStats.time =
6743         programStats.score = programStats.got_only_move = 0;
6744         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6745
6746         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6747         strcat(bookMove, bookHit);
6748         HandleMachineMove(bookMove, &first);
6749   }
6750   return 1;
6751 }
6752
6753 void
6754 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6755      Board board;
6756      int flags;
6757      ChessMove kind;
6758      int rf, ff, rt, ft;
6759      VOIDSTAR closure;
6760 {
6761     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6762     Markers *m = (Markers *) closure;
6763     if(rf == fromY && ff == fromX)
6764         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6765                          || kind == WhiteCapturesEnPassant
6766                          || kind == BlackCapturesEnPassant);
6767     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6768 }
6769
6770 void
6771 MarkTargetSquares(int clear)
6772 {
6773   int x, y;
6774   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6775      !appData.testLegality || gameMode == EditPosition) return;
6776   if(clear) {
6777     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6778   } else {
6779     int capt = 0;
6780     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6781     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6782       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6783       if(capt)
6784       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6785     }
6786   }
6787   DrawPosition(TRUE, NULL);
6788 }
6789
6790 int
6791 Explode(Board board, int fromX, int fromY, int toX, int toY)
6792 {
6793     if(gameInfo.variant == VariantAtomic &&
6794        (board[toY][toX] != EmptySquare ||                     // capture?
6795         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6796                          board[fromY][fromX] == BlackPawn   )
6797       )) {
6798         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6799         return TRUE;
6800     }
6801     return FALSE;
6802 }
6803
6804 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6805
6806 int CanPromote(ChessSquare piece, int y)
6807 {
6808         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6809         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6810         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6811            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6812            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6813                                                   gameInfo.variant == VariantMakruk) return FALSE;
6814         return (piece == BlackPawn && y == 1 ||
6815                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6816                 piece == BlackLance && y == 1 ||
6817                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6818 }
6819
6820 void LeftClick(ClickType clickType, int xPix, int yPix)
6821 {
6822     int x, y;
6823     Boolean saveAnimate;
6824     static int second = 0, promotionChoice = 0, clearFlag = 0;
6825     char promoChoice = NULLCHAR;
6826     ChessSquare piece;
6827
6828     if(appData.seekGraph && appData.icsActive && loggedOn &&
6829         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6830         SeekGraphClick(clickType, xPix, yPix, 0);
6831         return;
6832     }
6833
6834     if (clickType == Press) ErrorPopDown();
6835
6836     x = EventToSquare(xPix, BOARD_WIDTH);
6837     y = EventToSquare(yPix, BOARD_HEIGHT);
6838     if (!flipView && y >= 0) {
6839         y = BOARD_HEIGHT - 1 - y;
6840     }
6841     if (flipView && x >= 0) {
6842         x = BOARD_WIDTH - 1 - x;
6843     }
6844
6845     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6846         defaultPromoChoice = promoSweep;
6847         promoSweep = EmptySquare;   // terminate sweep
6848         promoDefaultAltered = TRUE;
6849         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6850     }
6851
6852     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6853         if(clickType == Release) return; // ignore upclick of click-click destination
6854         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6855         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6856         if(gameInfo.holdingsWidth &&
6857                 (WhiteOnMove(currentMove)
6858                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6859                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6860             // click in right holdings, for determining promotion piece
6861             ChessSquare p = boards[currentMove][y][x];
6862             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6863             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6864             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6865                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6866                 fromX = fromY = -1;
6867                 return;
6868             }
6869         }
6870         DrawPosition(FALSE, boards[currentMove]);
6871         return;
6872     }
6873
6874     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6875     if(clickType == Press
6876             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6877               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6878               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6879         return;
6880
6881     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6882         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6883
6884     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6885         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6886                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6887         defaultPromoChoice = DefaultPromoChoice(side);
6888     }
6889
6890     autoQueen = appData.alwaysPromoteToQueen;
6891
6892     if (fromX == -1) {
6893       int originalY = y;
6894       gatingPiece = EmptySquare;
6895       if (clickType != Press) {
6896         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6897             DragPieceEnd(xPix, yPix); dragging = 0;
6898             DrawPosition(FALSE, NULL);
6899         }
6900         return;
6901       }
6902       fromX = x; fromY = y; toX = toY = -1;
6903       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6904          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6905          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6906             /* First square */
6907             if (OKToStartUserMove(fromX, fromY)) {
6908                 second = 0;
6909                 MarkTargetSquares(0);
6910                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6911                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6912                     promoSweep = defaultPromoChoice;
6913                     selectFlag = 0; lastX = xPix; lastY = yPix;
6914                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6915                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6916                 }
6917                 if (appData.highlightDragging) {
6918                     SetHighlights(fromX, fromY, -1, -1);
6919                 }
6920             } else fromX = fromY = -1;
6921             return;
6922         }
6923     }
6924
6925     /* fromX != -1 */
6926     if (clickType == Press && gameMode != EditPosition) {
6927         ChessSquare fromP;
6928         ChessSquare toP;
6929         int frc;
6930
6931         // ignore off-board to clicks
6932         if(y < 0 || x < 0) return;
6933
6934         /* Check if clicking again on the same color piece */
6935         fromP = boards[currentMove][fromY][fromX];
6936         toP = boards[currentMove][y][x];
6937         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6938         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6939              WhitePawn <= toP && toP <= WhiteKing &&
6940              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6941              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6942             (BlackPawn <= fromP && fromP <= BlackKing &&
6943              BlackPawn <= toP && toP <= BlackKing &&
6944              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6945              !(fromP == BlackKing && toP == BlackRook && frc))) {
6946             /* Clicked again on same color piece -- changed his mind */
6947             second = (x == fromX && y == fromY);
6948             promoDefaultAltered = FALSE;
6949             MarkTargetSquares(1);
6950            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6951             if (appData.highlightDragging) {
6952                 SetHighlights(x, y, -1, -1);
6953             } else {
6954                 ClearHighlights();
6955             }
6956             if (OKToStartUserMove(x, y)) {
6957                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6958                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6959                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6960                  gatingPiece = boards[currentMove][fromY][fromX];
6961                 else gatingPiece = EmptySquare;
6962                 fromX = x;
6963                 fromY = y; dragging = 1;
6964                 MarkTargetSquares(0);
6965                 DragPieceBegin(xPix, yPix, FALSE);
6966                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6967                     promoSweep = defaultPromoChoice;
6968                     selectFlag = 0; lastX = xPix; lastY = yPix;
6969                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6970                 }
6971             }
6972            }
6973            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6974            second = FALSE; 
6975         }
6976         // ignore clicks on holdings
6977         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6978     }
6979
6980     if (clickType == Release && x == fromX && y == fromY) {
6981         DragPieceEnd(xPix, yPix); dragging = 0;
6982         if(clearFlag) {
6983             // a deferred attempt to click-click move an empty square on top of a piece
6984             boards[currentMove][y][x] = EmptySquare;
6985             ClearHighlights();
6986             DrawPosition(FALSE, boards[currentMove]);
6987             fromX = fromY = -1; clearFlag = 0;
6988             return;
6989         }
6990         if (appData.animateDragging) {
6991             /* Undo animation damage if any */
6992             DrawPosition(FALSE, NULL);
6993         }
6994         if (second) {
6995             /* Second up/down in same square; just abort move */
6996             second = 0;
6997             fromX = fromY = -1;
6998             gatingPiece = EmptySquare;
6999             ClearHighlights();
7000             gotPremove = 0;
7001             ClearPremoveHighlights();
7002         } else {
7003             /* First upclick in same square; start click-click mode */
7004             SetHighlights(x, y, -1, -1);
7005         }
7006         return;
7007     }
7008
7009     clearFlag = 0;
7010
7011     /* we now have a different from- and (possibly off-board) to-square */
7012     /* Completed move */
7013     toX = x;
7014     toY = y;
7015     saveAnimate = appData.animate;
7016     MarkTargetSquares(1);
7017     if (clickType == Press) {
7018         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7019             // must be Edit Position mode with empty-square selected
7020             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7021             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7022             return;
7023         }
7024         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7025             ChessSquare piece = boards[currentMove][fromY][fromX];
7026             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7027             promoSweep = defaultPromoChoice;
7028             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7029             selectFlag = 0; lastX = xPix; lastY = yPix;
7030             Sweep(0); // Pawn that is going to promote: preview promotion piece
7031             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7032             DrawPosition(FALSE, boards[currentMove]);
7033             return;
7034         }
7035         /* Finish clickclick move */
7036         if (appData.animate || appData.highlightLastMove) {
7037             SetHighlights(fromX, fromY, toX, toY);
7038         } else {
7039             ClearHighlights();
7040         }
7041     } else {
7042         /* Finish drag move */
7043         if (appData.highlightLastMove) {
7044             SetHighlights(fromX, fromY, toX, toY);
7045         } else {
7046             ClearHighlights();
7047         }
7048         DragPieceEnd(xPix, yPix); dragging = 0;
7049         /* Don't animate move and drag both */
7050         appData.animate = FALSE;
7051     }
7052
7053     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7054     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7055         ChessSquare piece = boards[currentMove][fromY][fromX];
7056         if(gameMode == EditPosition && piece != EmptySquare &&
7057            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7058             int n;
7059
7060             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7061                 n = PieceToNumber(piece - (int)BlackPawn);
7062                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7063                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7065             } else
7066             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7067                 n = PieceToNumber(piece);
7068                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7069                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7070                 boards[currentMove][n][BOARD_WIDTH-2]++;
7071             }
7072             boards[currentMove][fromY][fromX] = EmptySquare;
7073         }
7074         ClearHighlights();
7075         fromX = fromY = -1;
7076         DrawPosition(TRUE, boards[currentMove]);
7077         return;
7078     }
7079
7080     // off-board moves should not be highlighted
7081     if(x < 0 || y < 0) ClearHighlights();
7082
7083     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7084
7085     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7086         SetHighlights(fromX, fromY, toX, toY);
7087         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7088             // [HGM] super: promotion to captured piece selected from holdings
7089             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7090             promotionChoice = TRUE;
7091             // kludge follows to temporarily execute move on display, without promoting yet
7092             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7093             boards[currentMove][toY][toX] = p;
7094             DrawPosition(FALSE, boards[currentMove]);
7095             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7096             boards[currentMove][toY][toX] = q;
7097             DisplayMessage("Click in holdings to choose piece", "");
7098             return;
7099         }
7100         PromotionPopUp();
7101     } else {
7102         int oldMove = currentMove;
7103         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7104         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7105         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7106         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7107            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7108             DrawPosition(TRUE, boards[currentMove]);
7109         fromX = fromY = -1;
7110     }
7111     appData.animate = saveAnimate;
7112     if (appData.animate || appData.animateDragging) {
7113         /* Undo animation damage if needed */
7114         DrawPosition(FALSE, NULL);
7115     }
7116 }
7117
7118 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7119 {   // front-end-free part taken out of PieceMenuPopup
7120     int whichMenu; int xSqr, ySqr;
7121
7122     if(seekGraphUp) { // [HGM] seekgraph
7123         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7124         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7125         return -2;
7126     }
7127
7128     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7129          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7130         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7131         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7132         if(action == Press)   {
7133             originalFlip = flipView;
7134             flipView = !flipView; // temporarily flip board to see game from partners perspective
7135             DrawPosition(TRUE, partnerBoard);
7136             DisplayMessage(partnerStatus, "");
7137             partnerUp = TRUE;
7138         } else if(action == Release) {
7139             flipView = originalFlip;
7140             DrawPosition(TRUE, boards[currentMove]);
7141             partnerUp = FALSE;
7142         }
7143         return -2;
7144     }
7145
7146     xSqr = EventToSquare(x, BOARD_WIDTH);
7147     ySqr = EventToSquare(y, BOARD_HEIGHT);
7148     if (action == Release) {
7149         if(pieceSweep != EmptySquare) {
7150             EditPositionMenuEvent(pieceSweep, toX, toY);
7151             pieceSweep = EmptySquare;
7152         } else UnLoadPV(); // [HGM] pv
7153     }
7154     if (action != Press) return -2; // return code to be ignored
7155     switch (gameMode) {
7156       case IcsExamining:
7157         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7158       case EditPosition:
7159         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7160         if (xSqr < 0 || ySqr < 0) return -1;
7161         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7162         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7163         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7164         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7165         NextPiece(0);
7166         return 2; // grab
7167       case IcsObserving:
7168         if(!appData.icsEngineAnalyze) return -1;
7169       case IcsPlayingWhite:
7170       case IcsPlayingBlack:
7171         if(!appData.zippyPlay) goto noZip;
7172       case AnalyzeMode:
7173       case AnalyzeFile:
7174       case MachinePlaysWhite:
7175       case MachinePlaysBlack:
7176       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7177         if (!appData.dropMenu) {
7178           LoadPV(x, y);
7179           return 2; // flag front-end to grab mouse events
7180         }
7181         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7182            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7183       case EditGame:
7184       noZip:
7185         if (xSqr < 0 || ySqr < 0) return -1;
7186         if (!appData.dropMenu || appData.testLegality &&
7187             gameInfo.variant != VariantBughouse &&
7188             gameInfo.variant != VariantCrazyhouse) return -1;
7189         whichMenu = 1; // drop menu
7190         break;
7191       default:
7192         return -1;
7193     }
7194
7195     if (((*fromX = xSqr) < 0) ||
7196         ((*fromY = ySqr) < 0)) {
7197         *fromX = *fromY = -1;
7198         return -1;
7199     }
7200     if (flipView)
7201       *fromX = BOARD_WIDTH - 1 - *fromX;
7202     else
7203       *fromY = BOARD_HEIGHT - 1 - *fromY;
7204
7205     return whichMenu;
7206 }
7207
7208 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7209 {
7210 //    char * hint = lastHint;
7211     FrontEndProgramStats stats;
7212
7213     stats.which = cps == &first ? 0 : 1;
7214     stats.depth = cpstats->depth;
7215     stats.nodes = cpstats->nodes;
7216     stats.score = cpstats->score;
7217     stats.time = cpstats->time;
7218     stats.pv = cpstats->movelist;
7219     stats.hint = lastHint;
7220     stats.an_move_index = 0;
7221     stats.an_move_count = 0;
7222
7223     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7224         stats.hint = cpstats->move_name;
7225         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7226         stats.an_move_count = cpstats->nr_moves;
7227     }
7228
7229     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
7230
7231     SetProgramStats( &stats );
7232 }
7233
7234 void
7235 ClearEngineOutputPane(int which)
7236 {
7237     static FrontEndProgramStats dummyStats;
7238     dummyStats.which = which;
7239     dummyStats.pv = "#";
7240     SetProgramStats( &dummyStats );
7241 }
7242
7243 #define MAXPLAYERS 500
7244
7245 char *
7246 TourneyStandings(int display)
7247 {
7248     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7249     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7250     char result, *p, *names[MAXPLAYERS];
7251
7252     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7253         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7254     names[0] = p = strdup(appData.participants);
7255     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7256
7257     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7258
7259     while(result = appData.results[nr]) {
7260         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7261         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7262         wScore = bScore = 0;
7263         switch(result) {
7264           case '+': wScore = 2; break;
7265           case '-': bScore = 2; break;
7266           case '=': wScore = bScore = 1; break;
7267           case ' ':
7268           case '*': return strdup("busy"); // tourney not finished
7269         }
7270         score[w] += wScore;
7271         score[b] += bScore;
7272         games[w]++;
7273         games[b]++;
7274         nr++;
7275     }
7276     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7277     for(w=0; w<nPlayers; w++) {
7278         bScore = -1;
7279         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7280         ranking[w] = b; points[w] = bScore; score[b] = -2;
7281     }
7282     p = malloc(nPlayers*34+1);
7283     for(w=0; w<nPlayers && w<display; w++)
7284         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7285     free(names[0]);
7286     return p;
7287 }
7288
7289 void
7290 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7291 {       // count all piece types
7292         int p, f, r;
7293         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7294         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7295         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7296                 p = board[r][f];
7297                 pCnt[p]++;
7298                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7299                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7300                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7301                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7302                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7303                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7304         }
7305 }
7306
7307 int
7308 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7309 {
7310         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7311         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7312
7313         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7314         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7315         if(myPawns == 2 && nMine == 3) // KPP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7317         if(myPawns == 1 && nMine == 2) // KP
7318             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7319         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7320             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7321         if(myPawns) return FALSE;
7322         if(pCnt[WhiteRook+side])
7323             return pCnt[BlackRook-side] ||
7324                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7325                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7326                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7327         if(pCnt[WhiteCannon+side]) {
7328             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7329             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7330         }
7331         if(pCnt[WhiteKnight+side])
7332             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7333         return FALSE;
7334 }
7335
7336 int
7337 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7338 {
7339         VariantClass v = gameInfo.variant;
7340
7341         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7342         if(v == VariantShatranj) return TRUE; // always winnable through baring
7343         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7344         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7345
7346         if(v == VariantXiangqi) {
7347                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7348
7349                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7350                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7351                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7352                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7353                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7354                 if(stale) // we have at least one last-rank P plus perhaps C
7355                     return majors // KPKX
7356                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7357                 else // KCA*E*
7358                     return pCnt[WhiteFerz+side] // KCAK
7359                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7360                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7361                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7362
7363         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7364                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7365
7366                 if(nMine == 1) return FALSE; // bare King
7367                 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
7368                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7369                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7370                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7371                 if(pCnt[WhiteKnight+side])
7372                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7373                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7374                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7375                 if(nBishops)
7376                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7377                 if(pCnt[WhiteAlfil+side])
7378                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7379                 if(pCnt[WhiteWazir+side])
7380                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7381         }
7382
7383         return TRUE;
7384 }
7385
7386 int
7387 CompareWithRights(Board b1, Board b2)
7388 {
7389     int rights = 0;
7390     if(!CompareBoards(b1, b2)) return FALSE;
7391     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7392     /* compare castling rights */
7393     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7394            rights++; /* King lost rights, while rook still had them */
7395     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7396         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7397            rights++; /* but at least one rook lost them */
7398     }
7399     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7400            rights++;
7401     if( b1[CASTLING][5] != NoRights ) {
7402         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7403            rights++;
7404     }
7405     return rights == 0;
7406 }
7407
7408 int
7409 Adjudicate(ChessProgramState *cps)
7410 {       // [HGM] some adjudications useful with buggy engines
7411         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7412         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7413         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7414         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7415         int k, count = 0; static int bare = 1;
7416         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7417         Boolean canAdjudicate = !appData.icsActive;
7418
7419         // most tests only when we understand the game, i.e. legality-checking on
7420             if( appData.testLegality )
7421             {   /* [HGM] Some more adjudications for obstinate engines */
7422                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7423                 static int moveCount = 6;
7424                 ChessMove result;
7425                 char *reason = NULL;
7426
7427                 /* Count what is on board. */
7428                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7429
7430                 /* Some material-based adjudications that have to be made before stalemate test */
7431                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7432                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7433                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7434                      if(canAdjudicate && appData.checkMates) {
7435                          if(engineOpponent)
7436                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7437                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7438                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7439                          return 1;
7440                      }
7441                 }
7442
7443                 /* Bare King in Shatranj (loses) or Losers (wins) */
7444                 if( nrW == 1 || nrB == 1) {
7445                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7446                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7447                      if(canAdjudicate && appData.checkMates) {
7448                          if(engineOpponent)
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7450                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7451                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7452                          return 1;
7453                      }
7454                   } else
7455                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7456                   {    /* bare King */
7457                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7458                         if(canAdjudicate && appData.checkMates) {
7459                             /* but only adjudicate if adjudication enabled */
7460                             if(engineOpponent)
7461                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7462                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7463                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7464                             return 1;
7465                         }
7466                   }
7467                 } else bare = 1;
7468
7469
7470             // don't wait for engine to announce game end if we can judge ourselves
7471             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7472               case MT_CHECK:
7473                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7474                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7475                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7476                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7477                             checkCnt++;
7478                         if(checkCnt >= 2) {
7479                             reason = "Xboard adjudication: 3rd check";
7480                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7481                             break;
7482                         }
7483                     }
7484                 }
7485               case MT_NONE:
7486               default:
7487                 break;
7488               case MT_STALEMATE:
7489               case MT_STAINMATE:
7490                 reason = "Xboard adjudication: Stalemate";
7491                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7492                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7493                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7494                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7495                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7496                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7497                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7498                                                                         EP_CHECKMATE : EP_WINS);
7499                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7500                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7501                 }
7502                 break;
7503               case MT_CHECKMATE:
7504                 reason = "Xboard adjudication: Checkmate";
7505                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7506                 break;
7507             }
7508
7509                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7510                     case EP_STALEMATE:
7511                         result = GameIsDrawn; break;
7512                     case EP_CHECKMATE:
7513                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7514                     case EP_WINS:
7515                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7516                     default:
7517                         result = EndOfFile;
7518                 }
7519                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7520                     if(engineOpponent)
7521                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                     GameEnds( result, reason, GE_XBOARD );
7523                     return 1;
7524                 }
7525
7526                 /* Next absolutely insufficient mating material. */
7527                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7528                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7529                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7530
7531                      /* always flag draws, for judging claims */
7532                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7533
7534                      if(canAdjudicate && appData.materialDraws) {
7535                          /* but only adjudicate them if adjudication enabled */
7536                          if(engineOpponent) {
7537                            SendToProgram("force\n", engineOpponent); // suppress reply
7538                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7539                          }
7540                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7541                          return 1;
7542                      }
7543                 }
7544
7545                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7546                 if(gameInfo.variant == VariantXiangqi ?
7547                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7548                  : nrW + nrB == 4 &&
7549                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7550                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7551                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7552                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7553                    ) ) {
7554                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7555                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7556                           if(engineOpponent) {
7557                             SendToProgram("force\n", engineOpponent); // suppress reply
7558                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7559                           }
7560                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7561                           return 1;
7562                      }
7563                 } else moveCount = 6;
7564             }
7565         if (appData.debugMode) { int i;
7566             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7567                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7568                     appData.drawRepeats);
7569             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7570               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7571
7572         }
7573
7574         // Repetition draws and 50-move rule can be applied independently of legality testing
7575
7576                 /* Check for rep-draws */
7577                 count = 0;
7578                 for(k = forwardMostMove-2;
7579                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7580                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7581                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7582                     k-=2)
7583                 {   int rights=0;
7584                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7585                         /* compare castling rights */
7586                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7587                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7588                                 rights++; /* King lost rights, while rook still had them */
7589                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7590                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7591                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7592                                    rights++; /* but at least one rook lost them */
7593                         }
7594                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7595                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7596                                 rights++;
7597                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7598                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7599                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7600                                    rights++;
7601                         }
7602                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7603                             && appData.drawRepeats > 1) {
7604                              /* adjudicate after user-specified nr of repeats */
7605                              int result = GameIsDrawn;
7606                              char *details = "XBoard adjudication: repetition draw";
7607                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7608                                 // [HGM] xiangqi: check for forbidden perpetuals
7609                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7610                                 for(m=forwardMostMove; m>k; m-=2) {
7611                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7612                                         ourPerpetual = 0; // the current mover did not always check
7613                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7614                                         hisPerpetual = 0; // the opponent did not always check
7615                                 }
7616                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7617                                                                         ourPerpetual, hisPerpetual);
7618                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7619                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7620                                     details = "Xboard adjudication: perpetual checking";
7621                                 } else
7622                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7623                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7624                                 } else
7625                                 // Now check for perpetual chases
7626                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7627                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7628                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7629                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7630                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7631                                         details = "Xboard adjudication: perpetual chasing";
7632                                     } else
7633                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7634                                         break; // Abort repetition-checking loop.
7635                                 }
7636                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7637                              }
7638                              if(engineOpponent) {
7639                                SendToProgram("force\n", engineOpponent); // suppress reply
7640                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7641                              }
7642                              GameEnds( result, details, GE_XBOARD );
7643                              return 1;
7644                         }
7645                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7646                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7647                     }
7648                 }
7649
7650                 /* Now we test for 50-move draws. Determine ply count */
7651                 count = forwardMostMove;
7652                 /* look for last irreversble move */
7653                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7654                     count--;
7655                 /* if we hit starting position, add initial plies */
7656                 if( count == backwardMostMove )
7657                     count -= initialRulePlies;
7658                 count = forwardMostMove - count;
7659                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7660                         // adjust reversible move counter for checks in Xiangqi
7661                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7662                         if(i < backwardMostMove) i = backwardMostMove;
7663                         while(i <= forwardMostMove) {
7664                                 lastCheck = inCheck; // check evasion does not count
7665                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7666                                 if(inCheck || lastCheck) count--; // check does not count
7667                                 i++;
7668                         }
7669                 }
7670                 if( count >= 100)
7671                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7672                          /* this is used to judge if draw claims are legal */
7673                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7674                          if(engineOpponent) {
7675                            SendToProgram("force\n", engineOpponent); // suppress reply
7676                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7677                          }
7678                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7679                          return 1;
7680                 }
7681
7682                 /* if draw offer is pending, treat it as a draw claim
7683                  * when draw condition present, to allow engines a way to
7684                  * claim draws before making their move to avoid a race
7685                  * condition occurring after their move
7686                  */
7687                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7688                          char *p = NULL;
7689                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7690                              p = "Draw claim: 50-move rule";
7691                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7692                              p = "Draw claim: 3-fold repetition";
7693                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7694                              p = "Draw claim: insufficient mating material";
7695                          if( p != NULL && canAdjudicate) {
7696                              if(engineOpponent) {
7697                                SendToProgram("force\n", engineOpponent); // suppress reply
7698                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                              }
7700                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7701                              return 1;
7702                          }
7703                 }
7704
7705                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7706                     if(engineOpponent) {
7707                       SendToProgram("force\n", engineOpponent); // suppress reply
7708                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7709                     }
7710                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7711                     return 1;
7712                 }
7713         return 0;
7714 }
7715
7716 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7717 {   // [HGM] book: this routine intercepts moves to simulate book replies
7718     char *bookHit = NULL;
7719
7720     //first determine if the incoming move brings opponent into his book
7721     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7722         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7723     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7724     if(bookHit != NULL && !cps->bookSuspend) {
7725         // make sure opponent is not going to reply after receiving move to book position
7726         SendToProgram("force\n", cps);
7727         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7728     }
7729     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7730     // now arrange restart after book miss
7731     if(bookHit) {
7732         // after a book hit we never send 'go', and the code after the call to this routine
7733         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7734         char buf[MSG_SIZ], *move = bookHit;
7735         if(cps->useSAN) {
7736             int fromX, fromY, toX, toY;
7737             char promoChar;
7738             ChessMove moveType;
7739             move = buf + 30;
7740             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7741                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7742                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7743                                     PosFlags(forwardMostMove),
7744                                     fromY, fromX, toY, toX, promoChar, move);
7745             } else {
7746                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7747                 bookHit = NULL;
7748             }
7749         }
7750         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7751         SendToProgram(buf, cps);
7752         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7753     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7754         SendToProgram("go\n", cps);
7755         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7756     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7757         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7758             SendToProgram("go\n", cps);
7759         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7760     }
7761     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7762 }
7763
7764 char *savedMessage;
7765 ChessProgramState *savedState;
7766 void DeferredBookMove(void)
7767 {
7768         if(savedState->lastPing != savedState->lastPong)
7769                     ScheduleDelayedEvent(DeferredBookMove, 10);
7770         else
7771         HandleMachineMove(savedMessage, savedState);
7772 }
7773
7774 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7775
7776 void
7777 HandleMachineMove(message, cps)
7778      char *message;
7779      ChessProgramState *cps;
7780 {
7781     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7782     char realname[MSG_SIZ];
7783     int fromX, fromY, toX, toY;
7784     ChessMove moveType;
7785     char promoChar;
7786     char *p, *pv=buf1;
7787     int machineWhite;
7788     char *bookHit;
7789
7790     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7791         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7792         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7793             DisplayError(_("Invalid pairing from pairing engine"), 0);
7794             return;
7795         }
7796         pairingReceived = 1;
7797         NextMatchGame();
7798         return; // Skim the pairing messages here.
7799     }
7800
7801     cps->userError = 0;
7802
7803 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7804     /*
7805      * Kludge to ignore BEL characters
7806      */
7807     while (*message == '\007') message++;
7808
7809     /*
7810      * [HGM] engine debug message: ignore lines starting with '#' character
7811      */
7812     if(cps->debug && *message == '#') return;
7813
7814     /*
7815      * Look for book output
7816      */
7817     if (cps == &first && bookRequested) {
7818         if (message[0] == '\t' || message[0] == ' ') {
7819             /* Part of the book output is here; append it */
7820             strcat(bookOutput, message);
7821             strcat(bookOutput, "  \n");
7822             return;
7823         } else if (bookOutput[0] != NULLCHAR) {
7824             /* All of book output has arrived; display it */
7825             char *p = bookOutput;
7826             while (*p != NULLCHAR) {
7827                 if (*p == '\t') *p = ' ';
7828                 p++;
7829             }
7830             DisplayInformation(bookOutput);
7831             bookRequested = FALSE;
7832             /* Fall through to parse the current output */
7833         }
7834     }
7835
7836     /*
7837      * Look for machine move.
7838      */
7839     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7840         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7841     {
7842         /* This method is only useful on engines that support ping */
7843         if (cps->lastPing != cps->lastPong) {
7844           if (gameMode == BeginningOfGame) {
7845             /* Extra move from before last new; ignore */
7846             if (appData.debugMode) {
7847                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7848             }
7849           } else {
7850             if (appData.debugMode) {
7851                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7852                         cps->which, gameMode);
7853             }
7854
7855             SendToProgram("undo\n", cps);
7856           }
7857           return;
7858         }
7859
7860         switch (gameMode) {
7861           case BeginningOfGame:
7862             /* Extra move from before last reset; ignore */
7863             if (appData.debugMode) {
7864                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7865             }
7866             return;
7867
7868           case EndOfGame:
7869           case IcsIdle:
7870           default:
7871             /* Extra move after we tried to stop.  The mode test is
7872                not a reliable way of detecting this problem, but it's
7873                the best we can do on engines that don't support ping.
7874             */
7875             if (appData.debugMode) {
7876                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7877                         cps->which, gameMode);
7878             }
7879             SendToProgram("undo\n", cps);
7880             return;
7881
7882           case MachinePlaysWhite:
7883           case IcsPlayingWhite:
7884             machineWhite = TRUE;
7885             break;
7886
7887           case MachinePlaysBlack:
7888           case IcsPlayingBlack:
7889             machineWhite = FALSE;
7890             break;
7891
7892           case TwoMachinesPlay:
7893             machineWhite = (cps->twoMachinesColor[0] == 'w');
7894             break;
7895         }
7896         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7897             if (appData.debugMode) {
7898                 fprintf(debugFP,
7899                         "Ignoring move out of turn by %s, gameMode %d"
7900                         ", forwardMost %d\n",
7901                         cps->which, gameMode, forwardMostMove);
7902             }
7903             return;
7904         }
7905
7906     if (appData.debugMode) { int f = forwardMostMove;
7907         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7908                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7909                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7910     }
7911         if(cps->alphaRank) AlphaRank(machineMove, 4);
7912         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7913                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7914             /* Machine move could not be parsed; ignore it. */
7915           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7916                     machineMove, _(cps->which));
7917             DisplayError(buf1, 0);
7918             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7919                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7920             if (gameMode == TwoMachinesPlay) {
7921               GameEnds(machineWhite ? BlackWins : WhiteWins,
7922                        buf1, GE_XBOARD);
7923             }
7924             return;
7925         }
7926
7927         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7928         /* So we have to redo legality test with true e.p. status here,  */
7929         /* to make sure an illegal e.p. capture does not slip through,   */
7930         /* to cause a forfeit on a justified illegal-move complaint      */
7931         /* of the opponent.                                              */
7932         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7933            ChessMove moveType;
7934            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7935                              fromY, fromX, toY, toX, promoChar);
7936             if (appData.debugMode) {
7937                 int i;
7938                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7939                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7940                 fprintf(debugFP, "castling rights\n");
7941             }
7942             if(moveType == IllegalMove) {
7943               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7944                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7945                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7946                            buf1, GE_XBOARD);
7947                 return;
7948            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7949            /* [HGM] Kludge to handle engines that send FRC-style castling
7950               when they shouldn't (like TSCP-Gothic) */
7951            switch(moveType) {
7952              case WhiteASideCastleFR:
7953              case BlackASideCastleFR:
7954                toX+=2;
7955                currentMoveString[2]++;
7956                break;
7957              case WhiteHSideCastleFR:
7958              case BlackHSideCastleFR:
7959                toX--;
7960                currentMoveString[2]--;
7961                break;
7962              default: ; // nothing to do, but suppresses warning of pedantic compilers
7963            }
7964         }
7965         hintRequested = FALSE;
7966         lastHint[0] = NULLCHAR;
7967         bookRequested = FALSE;
7968         /* Program may be pondering now */
7969         cps->maybeThinking = TRUE;
7970         if (cps->sendTime == 2) cps->sendTime = 1;
7971         if (cps->offeredDraw) cps->offeredDraw--;
7972
7973         /* [AS] Save move info*/
7974         pvInfoList[ forwardMostMove ].score = programStats.score;
7975         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7976         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7977
7978         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7979
7980         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7981         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7982             int count = 0;
7983
7984             while( count < adjudicateLossPlies ) {
7985                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7986
7987                 if( count & 1 ) {
7988                     score = -score; /* Flip score for winning side */
7989                 }
7990
7991                 if( score > adjudicateLossThreshold ) {
7992                     break;
7993                 }
7994
7995                 count++;
7996             }
7997
7998             if( count >= adjudicateLossPlies ) {
7999                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8000
8001                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8002                     "Xboard adjudication",
8003                     GE_XBOARD );
8004
8005                 return;
8006             }
8007         }
8008
8009         if(Adjudicate(cps)) {
8010             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8011             return; // [HGM] adjudicate: for all automatic game ends
8012         }
8013
8014 #if ZIPPY
8015         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8016             first.initDone) {
8017           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8018                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8019                 SendToICS("draw ");
8020                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8021           }
8022           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8023           ics_user_moved = 1;
8024           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8025                 char buf[3*MSG_SIZ];
8026
8027                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8028                         programStats.score / 100.,
8029                         programStats.depth,
8030                         programStats.time / 100.,
8031                         (unsigned int)programStats.nodes,
8032                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8033                         programStats.movelist);
8034                 SendToICS(buf);
8035 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8036           }
8037         }
8038 #endif
8039
8040         /* [AS] Clear stats for next move */
8041         ClearProgramStats();
8042         thinkOutput[0] = NULLCHAR;
8043         hiddenThinkOutputState = 0;
8044
8045         bookHit = NULL;
8046         if (gameMode == TwoMachinesPlay) {
8047             /* [HGM] relaying draw offers moved to after reception of move */
8048             /* and interpreting offer as claim if it brings draw condition */
8049             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8050                 SendToProgram("draw\n", cps->other);
8051             }
8052             if (cps->other->sendTime) {
8053                 SendTimeRemaining(cps->other,
8054                                   cps->other->twoMachinesColor[0] == 'w');
8055             }
8056             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8057             if (firstMove && !bookHit) {
8058                 firstMove = FALSE;
8059                 if (cps->other->useColors) {
8060                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8061                 }
8062                 SendToProgram("go\n", cps->other);
8063             }
8064             cps->other->maybeThinking = TRUE;
8065         }
8066
8067         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8068
8069         if (!pausing && appData.ringBellAfterMoves) {
8070             RingBell();
8071         }
8072
8073         /*
8074          * Reenable menu items that were disabled while
8075          * machine was thinking
8076          */
8077         if (gameMode != TwoMachinesPlay)
8078             SetUserThinkingEnables();
8079
8080         // [HGM] book: after book hit opponent has received move and is now in force mode
8081         // force the book reply into it, and then fake that it outputted this move by jumping
8082         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8083         if(bookHit) {
8084                 static char bookMove[MSG_SIZ]; // a bit generous?
8085
8086                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8087                 strcat(bookMove, bookHit);
8088                 message = bookMove;
8089                 cps = cps->other;
8090                 programStats.nodes = programStats.depth = programStats.time =
8091                 programStats.score = programStats.got_only_move = 0;
8092                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8093
8094                 if(cps->lastPing != cps->lastPong) {
8095                     savedMessage = message; // args for deferred call
8096                     savedState = cps;
8097                     ScheduleDelayedEvent(DeferredBookMove, 10);
8098                     return;
8099                 }
8100                 goto FakeBookMove;
8101         }
8102
8103         return;
8104     }
8105
8106     /* Set special modes for chess engines.  Later something general
8107      *  could be added here; for now there is just one kludge feature,
8108      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8109      *  when "xboard" is given as an interactive command.
8110      */
8111     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8112         cps->useSigint = FALSE;
8113         cps->useSigterm = FALSE;
8114     }
8115     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8116       ParseFeatures(message+8, cps);
8117       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8118     }
8119
8120     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8121       int dummy, s=6; char buf[MSG_SIZ];
8122       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8123       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8124       ParseFEN(boards[0], &dummy, message+s);
8125       DrawPosition(TRUE, boards[0]);
8126       startedFromSetupPosition = TRUE;
8127       return;
8128     }
8129     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8130      * want this, I was asked to put it in, and obliged.
8131      */
8132     if (!strncmp(message, "setboard ", 9)) {
8133         Board initial_position;
8134
8135         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8136
8137         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8138             DisplayError(_("Bad FEN received from engine"), 0);
8139             return ;
8140         } else {
8141            Reset(TRUE, FALSE);
8142            CopyBoard(boards[0], initial_position);
8143            initialRulePlies = FENrulePlies;
8144            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8145            else gameMode = MachinePlaysBlack;
8146            DrawPosition(FALSE, boards[currentMove]);
8147         }
8148         return;
8149     }
8150
8151     /*
8152      * Look for communication commands
8153      */
8154     if (!strncmp(message, "telluser ", 9)) {
8155         if(message[9] == '\\' && message[10] == '\\')
8156             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8157         PlayTellSound();
8158         DisplayNote(message + 9);
8159         return;
8160     }
8161     if (!strncmp(message, "tellusererror ", 14)) {
8162         cps->userError = 1;
8163         if(message[14] == '\\' && message[15] == '\\')
8164             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8165         PlayTellSound();
8166         DisplayError(message + 14, 0);
8167         return;
8168     }
8169     if (!strncmp(message, "tellopponent ", 13)) {
8170       if (appData.icsActive) {
8171         if (loggedOn) {
8172           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8173           SendToICS(buf1);
8174         }
8175       } else {
8176         DisplayNote(message + 13);
8177       }
8178       return;
8179     }
8180     if (!strncmp(message, "tellothers ", 11)) {
8181       if (appData.icsActive) {
8182         if (loggedOn) {
8183           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8184           SendToICS(buf1);
8185         }
8186       }
8187       return;
8188     }
8189     if (!strncmp(message, "tellall ", 8)) {
8190       if (appData.icsActive) {
8191         if (loggedOn) {
8192           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8193           SendToICS(buf1);
8194         }
8195       } else {
8196         DisplayNote(message + 8);
8197       }
8198       return;
8199     }
8200     if (strncmp(message, "warning", 7) == 0) {
8201         /* Undocumented feature, use tellusererror in new code */
8202         DisplayError(message, 0);
8203         return;
8204     }
8205     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8206         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8207         strcat(realname, " query");
8208         AskQuestion(realname, buf2, buf1, cps->pr);
8209         return;
8210     }
8211     /* Commands from the engine directly to ICS.  We don't allow these to be
8212      *  sent until we are logged on. Crafty kibitzes have been known to
8213      *  interfere with the login process.
8214      */
8215     if (loggedOn) {
8216         if (!strncmp(message, "tellics ", 8)) {
8217             SendToICS(message + 8);
8218             SendToICS("\n");
8219             return;
8220         }
8221         if (!strncmp(message, "tellicsnoalias ", 15)) {
8222             SendToICS(ics_prefix);
8223             SendToICS(message + 15);
8224             SendToICS("\n");
8225             return;
8226         }
8227         /* The following are for backward compatibility only */
8228         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8229             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8230             SendToICS(ics_prefix);
8231             SendToICS(message);
8232             SendToICS("\n");
8233             return;
8234         }
8235     }
8236     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8237         return;
8238     }
8239     /*
8240      * If the move is illegal, cancel it and redraw the board.
8241      * Also deal with other error cases.  Matching is rather loose
8242      * here to accommodate engines written before the spec.
8243      */
8244     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8245         strncmp(message, "Error", 5) == 0) {
8246         if (StrStr(message, "name") ||
8247             StrStr(message, "rating") || StrStr(message, "?") ||
8248             StrStr(message, "result") || StrStr(message, "board") ||
8249             StrStr(message, "bk") || StrStr(message, "computer") ||
8250             StrStr(message, "variant") || StrStr(message, "hint") ||
8251             StrStr(message, "random") || StrStr(message, "depth") ||
8252             StrStr(message, "accepted")) {
8253             return;
8254         }
8255         if (StrStr(message, "protover")) {
8256           /* Program is responding to input, so it's apparently done
8257              initializing, and this error message indicates it is
8258              protocol version 1.  So we don't need to wait any longer
8259              for it to initialize and send feature commands. */
8260           FeatureDone(cps, 1);
8261           cps->protocolVersion = 1;
8262           return;
8263         }
8264         cps->maybeThinking = FALSE;
8265
8266         if (StrStr(message, "draw")) {
8267             /* Program doesn't have "draw" command */
8268             cps->sendDrawOffers = 0;
8269             return;
8270         }
8271         if (cps->sendTime != 1 &&
8272             (StrStr(message, "time") || StrStr(message, "otim"))) {
8273           /* Program apparently doesn't have "time" or "otim" command */
8274           cps->sendTime = 0;
8275           return;
8276         }
8277         if (StrStr(message, "analyze")) {
8278             cps->analysisSupport = FALSE;
8279             cps->analyzing = FALSE;
8280 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8281             EditGameEvent(); // [HGM] try to preserve loaded game
8282             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8283             DisplayError(buf2, 0);
8284             return;
8285         }
8286         if (StrStr(message, "(no matching move)st")) {
8287           /* Special kludge for GNU Chess 4 only */
8288           cps->stKludge = TRUE;
8289           SendTimeControl(cps, movesPerSession, timeControl,
8290                           timeIncrement, appData.searchDepth,
8291                           searchTime);
8292           return;
8293         }
8294         if (StrStr(message, "(no matching move)sd")) {
8295           /* Special kludge for GNU Chess 4 only */
8296           cps->sdKludge = TRUE;
8297           SendTimeControl(cps, movesPerSession, timeControl,
8298                           timeIncrement, appData.searchDepth,
8299                           searchTime);
8300           return;
8301         }
8302         if (!StrStr(message, "llegal")) {
8303             return;
8304         }
8305         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8306             gameMode == IcsIdle) return;
8307         if (forwardMostMove <= backwardMostMove) return;
8308         if (pausing) PauseEvent();
8309       if(appData.forceIllegal) {
8310             // [HGM] illegal: machine refused move; force position after move into it
8311           SendToProgram("force\n", cps);
8312           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8313                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8314                 // when black is to move, while there might be nothing on a2 or black
8315                 // might already have the move. So send the board as if white has the move.
8316                 // But first we must change the stm of the engine, as it refused the last move
8317                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8318                 if(WhiteOnMove(forwardMostMove)) {
8319                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8320                     SendBoard(cps, forwardMostMove); // kludgeless board
8321                 } else {
8322                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8323                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8324                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8325                 }
8326           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8327             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8328                  gameMode == TwoMachinesPlay)
8329               SendToProgram("go\n", cps);
8330             return;
8331       } else
8332         if (gameMode == PlayFromGameFile) {
8333             /* Stop reading this game file */
8334             gameMode = EditGame;
8335             ModeHighlight();
8336         }
8337         /* [HGM] illegal-move claim should forfeit game when Xboard */
8338         /* only passes fully legal moves                            */
8339         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8340             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8341                                 "False illegal-move claim", GE_XBOARD );
8342             return; // do not take back move we tested as valid
8343         }
8344         currentMove = forwardMostMove-1;
8345         DisplayMove(currentMove-1); /* before DisplayMoveError */
8346         SwitchClocks(forwardMostMove-1); // [HGM] race
8347         DisplayBothClocks();
8348         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8349                 parseList[currentMove], _(cps->which));
8350         DisplayMoveError(buf1);
8351         DrawPosition(FALSE, boards[currentMove]);
8352         return;
8353     }
8354     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8355         /* Program has a broken "time" command that
8356            outputs a string not ending in newline.
8357            Don't use it. */
8358         cps->sendTime = 0;
8359     }
8360
8361     /*
8362      * If chess program startup fails, exit with an error message.
8363      * Attempts to recover here are futile.
8364      */
8365     if ((StrStr(message, "unknown host") != NULL)
8366         || (StrStr(message, "No remote directory") != NULL)
8367         || (StrStr(message, "not found") != NULL)
8368         || (StrStr(message, "No such file") != NULL)
8369         || (StrStr(message, "can't alloc") != NULL)
8370         || (StrStr(message, "Permission denied") != NULL)) {
8371
8372         cps->maybeThinking = FALSE;
8373         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8374                 _(cps->which), cps->program, cps->host, message);
8375         RemoveInputSource(cps->isr);
8376         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8377             if(cps == &first) appData.noChessProgram = TRUE;
8378             DisplayError(buf1, 0);
8379         }
8380         return;
8381     }
8382
8383     /*
8384      * Look for hint output
8385      */
8386     if (sscanf(message, "Hint: %s", buf1) == 1) {
8387         if (cps == &first && hintRequested) {
8388             hintRequested = FALSE;
8389             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8390                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8391                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8392                                     PosFlags(forwardMostMove),
8393                                     fromY, fromX, toY, toX, promoChar, buf1);
8394                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8395                 DisplayInformation(buf2);
8396             } else {
8397                 /* Hint move could not be parsed!? */
8398               snprintf(buf2, sizeof(buf2),
8399                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8400                         buf1, _(cps->which));
8401                 DisplayError(buf2, 0);
8402             }
8403         } else {
8404           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8405         }
8406         return;
8407     }
8408
8409     /*
8410      * Ignore other messages if game is not in progress
8411      */
8412     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8413         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8414
8415     /*
8416      * look for win, lose, draw, or draw offer
8417      */
8418     if (strncmp(message, "1-0", 3) == 0) {
8419         char *p, *q, *r = "";
8420         p = strchr(message, '{');
8421         if (p) {
8422             q = strchr(p, '}');
8423             if (q) {
8424                 *q = NULLCHAR;
8425                 r = p + 1;
8426             }
8427         }
8428         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8429         return;
8430     } else if (strncmp(message, "0-1", 3) == 0) {
8431         char *p, *q, *r = "";
8432         p = strchr(message, '{');
8433         if (p) {
8434             q = strchr(p, '}');
8435             if (q) {
8436                 *q = NULLCHAR;
8437                 r = p + 1;
8438             }
8439         }
8440         /* Kludge for Arasan 4.1 bug */
8441         if (strcmp(r, "Black resigns") == 0) {
8442             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8443             return;
8444         }
8445         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8446         return;
8447     } else if (strncmp(message, "1/2", 3) == 0) {
8448         char *p, *q, *r = "";
8449         p = strchr(message, '{');
8450         if (p) {
8451             q = strchr(p, '}');
8452             if (q) {
8453                 *q = NULLCHAR;
8454                 r = p + 1;
8455             }
8456         }
8457
8458         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8459         return;
8460
8461     } else if (strncmp(message, "White resign", 12) == 0) {
8462         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8463         return;
8464     } else if (strncmp(message, "Black resign", 12) == 0) {
8465         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8466         return;
8467     } else if (strncmp(message, "White matches", 13) == 0 ||
8468                strncmp(message, "Black matches", 13) == 0   ) {
8469         /* [HGM] ignore GNUShogi noises */
8470         return;
8471     } else if (strncmp(message, "White", 5) == 0 &&
8472                message[5] != '(' &&
8473                StrStr(message, "Black") == NULL) {
8474         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8475         return;
8476     } else if (strncmp(message, "Black", 5) == 0 &&
8477                message[5] != '(') {
8478         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8479         return;
8480     } else if (strcmp(message, "resign") == 0 ||
8481                strcmp(message, "computer resigns") == 0) {
8482         switch (gameMode) {
8483           case MachinePlaysBlack:
8484           case IcsPlayingBlack:
8485             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8486             break;
8487           case MachinePlaysWhite:
8488           case IcsPlayingWhite:
8489             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8490             break;
8491           case TwoMachinesPlay:
8492             if (cps->twoMachinesColor[0] == 'w')
8493               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8494             else
8495               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8496             break;
8497           default:
8498             /* can't happen */
8499             break;
8500         }
8501         return;
8502     } else if (strncmp(message, "opponent mates", 14) == 0) {
8503         switch (gameMode) {
8504           case MachinePlaysBlack:
8505           case IcsPlayingBlack:
8506             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8507             break;
8508           case MachinePlaysWhite:
8509           case IcsPlayingWhite:
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8511             break;
8512           case TwoMachinesPlay:
8513             if (cps->twoMachinesColor[0] == 'w')
8514               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8515             else
8516               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8517             break;
8518           default:
8519             /* can't happen */
8520             break;
8521         }
8522         return;
8523     } else if (strncmp(message, "computer mates", 14) == 0) {
8524         switch (gameMode) {
8525           case MachinePlaysBlack:
8526           case IcsPlayingBlack:
8527             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8528             break;
8529           case MachinePlaysWhite:
8530           case IcsPlayingWhite:
8531             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8532             break;
8533           case TwoMachinesPlay:
8534             if (cps->twoMachinesColor[0] == 'w')
8535               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8536             else
8537               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8538             break;
8539           default:
8540             /* can't happen */
8541             break;
8542         }
8543         return;
8544     } else if (strncmp(message, "checkmate", 9) == 0) {
8545         if (WhiteOnMove(forwardMostMove)) {
8546             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8547         } else {
8548             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8549         }
8550         return;
8551     } else if (strstr(message, "Draw") != NULL ||
8552                strstr(message, "game is a draw") != NULL) {
8553         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8554         return;
8555     } else if (strstr(message, "offer") != NULL &&
8556                strstr(message, "draw") != NULL) {
8557 #if ZIPPY
8558         if (appData.zippyPlay && first.initDone) {
8559             /* Relay offer to ICS */
8560             SendToICS(ics_prefix);
8561             SendToICS("draw\n");
8562         }
8563 #endif
8564         cps->offeredDraw = 2; /* valid until this engine moves twice */
8565         if (gameMode == TwoMachinesPlay) {
8566             if (cps->other->offeredDraw) {
8567                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8568             /* [HGM] in two-machine mode we delay relaying draw offer      */
8569             /* until after we also have move, to see if it is really claim */
8570             }
8571         } else if (gameMode == MachinePlaysWhite ||
8572                    gameMode == MachinePlaysBlack) {
8573           if (userOfferedDraw) {
8574             DisplayInformation(_("Machine accepts your draw offer"));
8575             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8576           } else {
8577             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8578           }
8579         }
8580     }
8581
8582
8583     /*
8584      * Look for thinking output
8585      */
8586     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8587           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8588                                 ) {
8589         int plylev, mvleft, mvtot, curscore, time;
8590         char mvname[MOVE_LEN];
8591         u64 nodes; // [DM]
8592         char plyext;
8593         int ignore = FALSE;
8594         int prefixHint = FALSE;
8595         mvname[0] = NULLCHAR;
8596
8597         switch (gameMode) {
8598           case MachinePlaysBlack:
8599           case IcsPlayingBlack:
8600             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8601             break;
8602           case MachinePlaysWhite:
8603           case IcsPlayingWhite:
8604             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8605             break;
8606           case AnalyzeMode:
8607           case AnalyzeFile:
8608             break;
8609           case IcsObserving: /* [DM] icsEngineAnalyze */
8610             if (!appData.icsEngineAnalyze) ignore = TRUE;
8611             break;
8612           case TwoMachinesPlay:
8613             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8614                 ignore = TRUE;
8615             }
8616             break;
8617           default:
8618             ignore = TRUE;
8619             break;
8620         }
8621
8622         if (!ignore) {
8623             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8624             buf1[0] = NULLCHAR;
8625             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8626                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8627
8628                 if (plyext != ' ' && plyext != '\t') {
8629                     time *= 100;
8630                 }
8631
8632                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8633                 if( cps->scoreIsAbsolute &&
8634                     ( gameMode == MachinePlaysBlack ||
8635                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8636                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8637                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8638                      !WhiteOnMove(currentMove)
8639                     ) )
8640                 {
8641                     curscore = -curscore;
8642                 }
8643
8644                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8645
8646                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8647                         char buf[MSG_SIZ];
8648                         FILE *f;
8649                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8650                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8651                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8652                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8653                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8654                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8655                                 fclose(f);
8656                         } else DisplayError("failed writing PV", 0);
8657                 }
8658
8659                 tempStats.depth = plylev;
8660                 tempStats.nodes = nodes;
8661                 tempStats.time = time;
8662                 tempStats.score = curscore;
8663                 tempStats.got_only_move = 0;
8664
8665                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8666                         int ticklen;
8667
8668                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8669                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8670                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8671                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8672                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8673                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8674                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8675                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8676                 }
8677
8678                 /* Buffer overflow protection */
8679                 if (pv[0] != NULLCHAR) {
8680                     if (strlen(pv) >= sizeof(tempStats.movelist)
8681                         && appData.debugMode) {
8682                         fprintf(debugFP,
8683                                 "PV is too long; using the first %u bytes.\n",
8684                                 (unsigned) sizeof(tempStats.movelist) - 1);
8685                     }
8686
8687                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8688                 } else {
8689                     sprintf(tempStats.movelist, " no PV\n");
8690                 }
8691
8692                 if (tempStats.seen_stat) {
8693                     tempStats.ok_to_send = 1;
8694                 }
8695
8696                 if (strchr(tempStats.movelist, '(') != NULL) {
8697                     tempStats.line_is_book = 1;
8698                     tempStats.nr_moves = 0;
8699                     tempStats.moves_left = 0;
8700                 } else {
8701                     tempStats.line_is_book = 0;
8702                 }
8703
8704                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8705                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8706
8707                 SendProgramStatsToFrontend( cps, &tempStats );
8708
8709                 /*
8710                     [AS] Protect the thinkOutput buffer from overflow... this
8711                     is only useful if buf1 hasn't overflowed first!
8712                 */
8713                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8714                          plylev,
8715                          (gameMode == TwoMachinesPlay ?
8716                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8717                          ((double) curscore) / 100.0,
8718                          prefixHint ? lastHint : "",
8719                          prefixHint ? " " : "" );
8720
8721                 if( buf1[0] != NULLCHAR ) {
8722                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8723
8724                     if( strlen(pv) > max_len ) {
8725                         if( appData.debugMode) {
8726                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8727                         }
8728                         pv[max_len+1] = '\0';
8729                     }
8730
8731                     strcat( thinkOutput, pv);
8732                 }
8733
8734                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8735                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8736                     DisplayMove(currentMove - 1);
8737                 }
8738                 return;
8739
8740             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8741                 /* crafty (9.25+) says "(only move) <move>"
8742                  * if there is only 1 legal move
8743                  */
8744                 sscanf(p, "(only move) %s", buf1);
8745                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8746                 sprintf(programStats.movelist, "%s (only move)", buf1);
8747                 programStats.depth = 1;
8748                 programStats.nr_moves = 1;
8749                 programStats.moves_left = 1;
8750                 programStats.nodes = 1;
8751                 programStats.time = 1;
8752                 programStats.got_only_move = 1;
8753
8754                 /* Not really, but we also use this member to
8755                    mean "line isn't going to change" (Crafty
8756                    isn't searching, so stats won't change) */
8757                 programStats.line_is_book = 1;
8758
8759                 SendProgramStatsToFrontend( cps, &programStats );
8760
8761                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8762                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8763                     DisplayMove(currentMove - 1);
8764                 }
8765                 return;
8766             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8767                               &time, &nodes, &plylev, &mvleft,
8768                               &mvtot, mvname) >= 5) {
8769                 /* The stat01: line is from Crafty (9.29+) in response
8770                    to the "." command */
8771                 programStats.seen_stat = 1;
8772                 cps->maybeThinking = TRUE;
8773
8774                 if (programStats.got_only_move || !appData.periodicUpdates)
8775                   return;
8776
8777                 programStats.depth = plylev;
8778                 programStats.time = time;
8779                 programStats.nodes = nodes;
8780                 programStats.moves_left = mvleft;
8781                 programStats.nr_moves = mvtot;
8782                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8783                 programStats.ok_to_send = 1;
8784                 programStats.movelist[0] = '\0';
8785
8786                 SendProgramStatsToFrontend( cps, &programStats );
8787
8788                 return;
8789
8790             } else if (strncmp(message,"++",2) == 0) {
8791                 /* Crafty 9.29+ outputs this */
8792                 programStats.got_fail = 2;
8793                 return;
8794
8795             } else if (strncmp(message,"--",2) == 0) {
8796                 /* Crafty 9.29+ outputs this */
8797                 programStats.got_fail = 1;
8798                 return;
8799
8800             } else if (thinkOutput[0] != NULLCHAR &&
8801                        strncmp(message, "    ", 4) == 0) {
8802                 unsigned message_len;
8803
8804                 p = message;
8805                 while (*p && *p == ' ') p++;
8806
8807                 message_len = strlen( p );
8808
8809                 /* [AS] Avoid buffer overflow */
8810                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8811                     strcat(thinkOutput, " ");
8812                     strcat(thinkOutput, p);
8813                 }
8814
8815                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8816                     strcat(programStats.movelist, " ");
8817                     strcat(programStats.movelist, p);
8818                 }
8819
8820                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8821                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8822                     DisplayMove(currentMove - 1);
8823                 }
8824                 return;
8825             }
8826         }
8827         else {
8828             buf1[0] = NULLCHAR;
8829
8830             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8831                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8832             {
8833                 ChessProgramStats cpstats;
8834
8835                 if (plyext != ' ' && plyext != '\t') {
8836                     time *= 100;
8837                 }
8838
8839                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8840                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8841                     curscore = -curscore;
8842                 }
8843
8844                 cpstats.depth = plylev;
8845                 cpstats.nodes = nodes;
8846                 cpstats.time = time;
8847                 cpstats.score = curscore;
8848                 cpstats.got_only_move = 0;
8849                 cpstats.movelist[0] = '\0';
8850
8851                 if (buf1[0] != NULLCHAR) {
8852                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8853                 }
8854
8855                 cpstats.ok_to_send = 0;
8856                 cpstats.line_is_book = 0;
8857                 cpstats.nr_moves = 0;
8858                 cpstats.moves_left = 0;
8859
8860                 SendProgramStatsToFrontend( cps, &cpstats );
8861             }
8862         }
8863     }
8864 }
8865
8866
8867 /* Parse a game score from the character string "game", and
8868    record it as the history of the current game.  The game
8869    score is NOT assumed to start from the standard position.
8870    The display is not updated in any way.
8871    */
8872 void
8873 ParseGameHistory(game)
8874      char *game;
8875 {
8876     ChessMove moveType;
8877     int fromX, fromY, toX, toY, boardIndex;
8878     char promoChar;
8879     char *p, *q;
8880     char buf[MSG_SIZ];
8881
8882     if (appData.debugMode)
8883       fprintf(debugFP, "Parsing game history: %s\n", game);
8884
8885     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8886     gameInfo.site = StrSave(appData.icsHost);
8887     gameInfo.date = PGNDate();
8888     gameInfo.round = StrSave("-");
8889
8890     /* Parse out names of players */
8891     while (*game == ' ') game++;
8892     p = buf;
8893     while (*game != ' ') *p++ = *game++;
8894     *p = NULLCHAR;
8895     gameInfo.white = StrSave(buf);
8896     while (*game == ' ') game++;
8897     p = buf;
8898     while (*game != ' ' && *game != '\n') *p++ = *game++;
8899     *p = NULLCHAR;
8900     gameInfo.black = StrSave(buf);
8901
8902     /* Parse moves */
8903     boardIndex = blackPlaysFirst ? 1 : 0;
8904     yynewstr(game);
8905     for (;;) {
8906         yyboardindex = boardIndex;
8907         moveType = (ChessMove) Myylex();
8908         switch (moveType) {
8909           case IllegalMove:             /* maybe suicide chess, etc. */
8910   if (appData.debugMode) {
8911     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8912     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8913     setbuf(debugFP, NULL);
8914   }
8915           case WhitePromotion:
8916           case BlackPromotion:
8917           case WhiteNonPromotion:
8918           case BlackNonPromotion:
8919           case NormalMove:
8920           case WhiteCapturesEnPassant:
8921           case BlackCapturesEnPassant:
8922           case WhiteKingSideCastle:
8923           case WhiteQueenSideCastle:
8924           case BlackKingSideCastle:
8925           case BlackQueenSideCastle:
8926           case WhiteKingSideCastleWild:
8927           case WhiteQueenSideCastleWild:
8928           case BlackKingSideCastleWild:
8929           case BlackQueenSideCastleWild:
8930           /* PUSH Fabien */
8931           case WhiteHSideCastleFR:
8932           case WhiteASideCastleFR:
8933           case BlackHSideCastleFR:
8934           case BlackASideCastleFR:
8935           /* POP Fabien */
8936             fromX = currentMoveString[0] - AAA;
8937             fromY = currentMoveString[1] - ONE;
8938             toX = currentMoveString[2] - AAA;
8939             toY = currentMoveString[3] - ONE;
8940             promoChar = currentMoveString[4];
8941             break;
8942           case WhiteDrop:
8943           case BlackDrop:
8944             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8945             fromX = moveType == WhiteDrop ?
8946               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8947             (int) CharToPiece(ToLower(currentMoveString[0]));
8948             fromY = DROP_RANK;
8949             toX = currentMoveString[2] - AAA;
8950             toY = currentMoveString[3] - ONE;
8951             promoChar = NULLCHAR;
8952             break;
8953           case AmbiguousMove:
8954             /* bug? */
8955             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8956   if (appData.debugMode) {
8957     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8958     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8959     setbuf(debugFP, NULL);
8960   }
8961             DisplayError(buf, 0);
8962             return;
8963           case ImpossibleMove:
8964             /* bug? */
8965             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8966   if (appData.debugMode) {
8967     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8968     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8969     setbuf(debugFP, NULL);
8970   }
8971             DisplayError(buf, 0);
8972             return;
8973           case EndOfFile:
8974             if (boardIndex < backwardMostMove) {
8975                 /* Oops, gap.  How did that happen? */
8976                 DisplayError(_("Gap in move list"), 0);
8977                 return;
8978             }
8979             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8980             if (boardIndex > forwardMostMove) {
8981                 forwardMostMove = boardIndex;
8982             }
8983             return;
8984           case ElapsedTime:
8985             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8986                 strcat(parseList[boardIndex-1], " ");
8987                 strcat(parseList[boardIndex-1], yy_text);
8988             }
8989             continue;
8990           case Comment:
8991           case PGNTag:
8992           case NAG:
8993           default:
8994             /* ignore */
8995             continue;
8996           case WhiteWins:
8997           case BlackWins:
8998           case GameIsDrawn:
8999           case GameUnfinished:
9000             if (gameMode == IcsExamining) {
9001                 if (boardIndex < backwardMostMove) {
9002                     /* Oops, gap.  How did that happen? */
9003                     return;
9004                 }
9005                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9006                 return;
9007             }
9008             gameInfo.result = moveType;
9009             p = strchr(yy_text, '{');
9010             if (p == NULL) p = strchr(yy_text, '(');
9011             if (p == NULL) {
9012                 p = yy_text;
9013                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9014             } else {
9015                 q = strchr(p, *p == '{' ? '}' : ')');
9016                 if (q != NULL) *q = NULLCHAR;
9017                 p++;
9018             }
9019             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9020             gameInfo.resultDetails = StrSave(p);
9021             continue;
9022         }
9023         if (boardIndex >= forwardMostMove &&
9024             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9025             backwardMostMove = blackPlaysFirst ? 1 : 0;
9026             return;
9027         }
9028         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9029                                  fromY, fromX, toY, toX, promoChar,
9030                                  parseList[boardIndex]);
9031         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9032         /* currentMoveString is set as a side-effect of yylex */
9033         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9034         strcat(moveList[boardIndex], "\n");
9035         boardIndex++;
9036         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9037         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9038           case MT_NONE:
9039           case MT_STALEMATE:
9040           default:
9041             break;
9042           case MT_CHECK:
9043             if(gameInfo.variant != VariantShogi)
9044                 strcat(parseList[boardIndex - 1], "+");
9045             break;
9046           case MT_CHECKMATE:
9047           case MT_STAINMATE:
9048             strcat(parseList[boardIndex - 1], "#");
9049             break;
9050         }
9051     }
9052 }
9053
9054
9055 /* Apply a move to the given board  */
9056 void
9057 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9058      int fromX, fromY, toX, toY;
9059      int promoChar;
9060      Board board;
9061 {
9062   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9063   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9064
9065     /* [HGM] compute & store e.p. status and castling rights for new position */
9066     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9067
9068       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9069       oldEP = (signed char)board[EP_STATUS];
9070       board[EP_STATUS] = EP_NONE;
9071
9072   if (fromY == DROP_RANK) {
9073         /* must be first */
9074         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9075             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9076             return;
9077         }
9078         piece = board[toY][toX] = (ChessSquare) fromX;
9079   } else {
9080       int i;
9081
9082       if( board[toY][toX] != EmptySquare )
9083            board[EP_STATUS] = EP_CAPTURE;
9084
9085       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9086            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9087                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9088       } else
9089       if( board[fromY][fromX] == WhitePawn ) {
9090            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9091                board[EP_STATUS] = EP_PAWN_MOVE;
9092            if( toY-fromY==2) {
9093                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9094                         gameInfo.variant != VariantBerolina || toX < fromX)
9095                       board[EP_STATUS] = toX | berolina;
9096                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9097                         gameInfo.variant != VariantBerolina || toX > fromX)
9098                       board[EP_STATUS] = toX;
9099            }
9100       } else
9101       if( board[fromY][fromX] == BlackPawn ) {
9102            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9103                board[EP_STATUS] = EP_PAWN_MOVE;
9104            if( toY-fromY== -2) {
9105                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9106                         gameInfo.variant != VariantBerolina || toX < fromX)
9107                       board[EP_STATUS] = toX | berolina;
9108                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9109                         gameInfo.variant != VariantBerolina || toX > fromX)
9110                       board[EP_STATUS] = toX;
9111            }
9112        }
9113
9114        for(i=0; i<nrCastlingRights; i++) {
9115            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9116               board[CASTLING][i] == toX   && castlingRank[i] == toY
9117              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9118        }
9119
9120      if (fromX == toX && fromY == toY) return;
9121
9122      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9123      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9124      if(gameInfo.variant == VariantKnightmate)
9125          king += (int) WhiteUnicorn - (int) WhiteKing;
9126
9127     /* Code added by Tord: */
9128     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9129     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9130         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9131       board[fromY][fromX] = EmptySquare;
9132       board[toY][toX] = EmptySquare;
9133       if((toX > fromX) != (piece == WhiteRook)) {
9134         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9135       } else {
9136         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9137       }
9138     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9139                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9140       board[fromY][fromX] = EmptySquare;
9141       board[toY][toX] = EmptySquare;
9142       if((toX > fromX) != (piece == BlackRook)) {
9143         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9144       } else {
9145         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9146       }
9147     /* End of code added by Tord */
9148
9149     } else if (board[fromY][fromX] == king
9150         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9151         && toY == fromY && toX > fromX+1) {
9152         board[fromY][fromX] = EmptySquare;
9153         board[toY][toX] = king;
9154         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9155         board[fromY][BOARD_RGHT-1] = EmptySquare;
9156     } else if (board[fromY][fromX] == king
9157         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9158                && toY == fromY && toX < fromX-1) {
9159         board[fromY][fromX] = EmptySquare;
9160         board[toY][toX] = king;
9161         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9162         board[fromY][BOARD_LEFT] = EmptySquare;
9163     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9164                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9165                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9166                ) {
9167         /* white pawn promotion */
9168         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9169         if(gameInfo.variant==VariantBughouse ||
9170            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9171             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9172         board[fromY][fromX] = EmptySquare;
9173     } else if ((fromY >= BOARD_HEIGHT>>1)
9174                && (toX != fromX)
9175                && gameInfo.variant != VariantXiangqi
9176                && gameInfo.variant != VariantBerolina
9177                && (board[fromY][fromX] == WhitePawn)
9178                && (board[toY][toX] == EmptySquare)) {
9179         board[fromY][fromX] = EmptySquare;
9180         board[toY][toX] = WhitePawn;
9181         captured = board[toY - 1][toX];
9182         board[toY - 1][toX] = EmptySquare;
9183     } else if ((fromY == BOARD_HEIGHT-4)
9184                && (toX == fromX)
9185                && gameInfo.variant == VariantBerolina
9186                && (board[fromY][fromX] == WhitePawn)
9187                && (board[toY][toX] == EmptySquare)) {
9188         board[fromY][fromX] = EmptySquare;
9189         board[toY][toX] = WhitePawn;
9190         if(oldEP & EP_BEROLIN_A) {
9191                 captured = board[fromY][fromX-1];
9192                 board[fromY][fromX-1] = EmptySquare;
9193         }else{  captured = board[fromY][fromX+1];
9194                 board[fromY][fromX+1] = EmptySquare;
9195         }
9196     } else if (board[fromY][fromX] == king
9197         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9198                && toY == fromY && toX > fromX+1) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = king;
9201         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9202         board[fromY][BOARD_RGHT-1] = EmptySquare;
9203     } else if (board[fromY][fromX] == king
9204         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9205                && toY == fromY && toX < fromX-1) {
9206         board[fromY][fromX] = EmptySquare;
9207         board[toY][toX] = king;
9208         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9209         board[fromY][BOARD_LEFT] = EmptySquare;
9210     } else if (fromY == 7 && fromX == 3
9211                && board[fromY][fromX] == BlackKing
9212                && toY == 7 && toX == 5) {
9213         board[fromY][fromX] = EmptySquare;
9214         board[toY][toX] = BlackKing;
9215         board[fromY][7] = EmptySquare;
9216         board[toY][4] = BlackRook;
9217     } else if (fromY == 7 && fromX == 3
9218                && board[fromY][fromX] == BlackKing
9219                && toY == 7 && toX == 1) {
9220         board[fromY][fromX] = EmptySquare;
9221         board[toY][toX] = BlackKing;
9222         board[fromY][0] = EmptySquare;
9223         board[toY][2] = BlackRook;
9224     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9225                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9226                && toY < promoRank && promoChar
9227                ) {
9228         /* black pawn promotion */
9229         board[toY][toX] = CharToPiece(ToLower(promoChar));
9230         if(gameInfo.variant==VariantBughouse ||
9231            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9232             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9233         board[fromY][fromX] = EmptySquare;
9234     } else if ((fromY < BOARD_HEIGHT>>1)
9235                && (toX != fromX)
9236                && gameInfo.variant != VariantXiangqi
9237                && gameInfo.variant != VariantBerolina
9238                && (board[fromY][fromX] == BlackPawn)
9239                && (board[toY][toX] == EmptySquare)) {
9240         board[fromY][fromX] = EmptySquare;
9241         board[toY][toX] = BlackPawn;
9242         captured = board[toY + 1][toX];
9243         board[toY + 1][toX] = EmptySquare;
9244     } else if ((fromY == 3)
9245                && (toX == fromX)
9246                && gameInfo.variant == VariantBerolina
9247                && (board[fromY][fromX] == BlackPawn)
9248                && (board[toY][toX] == EmptySquare)) {
9249         board[fromY][fromX] = EmptySquare;
9250         board[toY][toX] = BlackPawn;
9251         if(oldEP & EP_BEROLIN_A) {
9252                 captured = board[fromY][fromX-1];
9253                 board[fromY][fromX-1] = EmptySquare;
9254         }else{  captured = board[fromY][fromX+1];
9255                 board[fromY][fromX+1] = EmptySquare;
9256         }
9257     } else {
9258         board[toY][toX] = board[fromY][fromX];
9259         board[fromY][fromX] = EmptySquare;
9260     }
9261   }
9262
9263     if (gameInfo.holdingsWidth != 0) {
9264
9265       /* !!A lot more code needs to be written to support holdings  */
9266       /* [HGM] OK, so I have written it. Holdings are stored in the */
9267       /* penultimate board files, so they are automaticlly stored   */
9268       /* in the game history.                                       */
9269       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9270                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9271         /* Delete from holdings, by decreasing count */
9272         /* and erasing image if necessary            */
9273         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9274         if(p < (int) BlackPawn) { /* white drop */
9275              p -= (int)WhitePawn;
9276                  p = PieceToNumber((ChessSquare)p);
9277              if(p >= gameInfo.holdingsSize) p = 0;
9278              if(--board[p][BOARD_WIDTH-2] <= 0)
9279                   board[p][BOARD_WIDTH-1] = EmptySquare;
9280              if((int)board[p][BOARD_WIDTH-2] < 0)
9281                         board[p][BOARD_WIDTH-2] = 0;
9282         } else {                  /* black drop */
9283              p -= (int)BlackPawn;
9284                  p = PieceToNumber((ChessSquare)p);
9285              if(p >= gameInfo.holdingsSize) p = 0;
9286              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9287                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9288              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9289                         board[BOARD_HEIGHT-1-p][1] = 0;
9290         }
9291       }
9292       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9293           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9294         /* [HGM] holdings: Add to holdings, if holdings exist */
9295         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9296                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9297                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9298         }
9299         p = (int) captured;
9300         if (p >= (int) BlackPawn) {
9301           p -= (int)BlackPawn;
9302           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9303                   /* in Shogi restore piece to its original  first */
9304                   captured = (ChessSquare) (DEMOTED captured);
9305                   p = DEMOTED p;
9306           }
9307           p = PieceToNumber((ChessSquare)p);
9308           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9309           board[p][BOARD_WIDTH-2]++;
9310           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9311         } else {
9312           p -= (int)WhitePawn;
9313           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9314                   captured = (ChessSquare) (DEMOTED captured);
9315                   p = DEMOTED p;
9316           }
9317           p = PieceToNumber((ChessSquare)p);
9318           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9319           board[BOARD_HEIGHT-1-p][1]++;
9320           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9321         }
9322       }
9323     } else if (gameInfo.variant == VariantAtomic) {
9324       if (captured != EmptySquare) {
9325         int y, x;
9326         for (y = toY-1; y <= toY+1; y++) {
9327           for (x = toX-1; x <= toX+1; x++) {
9328             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9329                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9330               board[y][x] = EmptySquare;
9331             }
9332           }
9333         }
9334         board[toY][toX] = EmptySquare;
9335       }
9336     }
9337     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9338         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9339     } else
9340     if(promoChar == '+') {
9341         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9342         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9343     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9344         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9345     }
9346     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9347                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9348         // [HGM] superchess: take promotion piece out of holdings
9349         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9350         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9351             if(!--board[k][BOARD_WIDTH-2])
9352                 board[k][BOARD_WIDTH-1] = EmptySquare;
9353         } else {
9354             if(!--board[BOARD_HEIGHT-1-k][1])
9355                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9356         }
9357     }
9358
9359 }
9360
9361 /* Updates forwardMostMove */
9362 void
9363 MakeMove(fromX, fromY, toX, toY, promoChar)
9364      int fromX, fromY, toX, toY;
9365      int promoChar;
9366 {
9367 //    forwardMostMove++; // [HGM] bare: moved downstream
9368
9369     (void) CoordsToAlgebraic(boards[forwardMostMove],
9370                              PosFlags(forwardMostMove),
9371                              fromY, fromX, toY, toX, promoChar,
9372                              parseList[forwardMostMove]);
9373
9374     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9375         int timeLeft; static int lastLoadFlag=0; int king, piece;
9376         piece = boards[forwardMostMove][fromY][fromX];
9377         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9378         if(gameInfo.variant == VariantKnightmate)
9379             king += (int) WhiteUnicorn - (int) WhiteKing;
9380         if(forwardMostMove == 0) {
9381             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9382                 fprintf(serverMoves, "%s;", UserName());
9383             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9384                 fprintf(serverMoves, "%s;", second.tidy);
9385             fprintf(serverMoves, "%s;", first.tidy);
9386             if(gameMode == MachinePlaysWhite)
9387                 fprintf(serverMoves, "%s;", UserName());
9388             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9389                 fprintf(serverMoves, "%s;", second.tidy);
9390         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9391         lastLoadFlag = loadFlag;
9392         // print base move
9393         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9394         // print castling suffix
9395         if( toY == fromY && piece == king ) {
9396             if(toX-fromX > 1)
9397                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9398             if(fromX-toX >1)
9399                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9400         }
9401         // e.p. suffix
9402         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9403              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9404              boards[forwardMostMove][toY][toX] == EmptySquare
9405              && fromX != toX && fromY != toY)
9406                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9407         // promotion suffix
9408         if(promoChar != NULLCHAR)
9409                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9410         if(!loadFlag) {
9411                 char buf[MOVE_LEN*2], *p; int len;
9412             fprintf(serverMoves, "/%d/%d",
9413                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9414             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9415             else                      timeLeft = blackTimeRemaining/1000;
9416             fprintf(serverMoves, "/%d", timeLeft);
9417                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9418                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9419                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9420             fprintf(serverMoves, "/%s", buf);
9421         }
9422         fflush(serverMoves);
9423     }
9424
9425     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9426         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9427       return;
9428     }
9429     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9430     if (commentList[forwardMostMove+1] != NULL) {
9431         free(commentList[forwardMostMove+1]);
9432         commentList[forwardMostMove+1] = NULL;
9433     }
9434     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9435     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9436     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9437     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9438     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9439     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9440     gameInfo.result = GameUnfinished;
9441     if (gameInfo.resultDetails != NULL) {
9442         free(gameInfo.resultDetails);
9443         gameInfo.resultDetails = NULL;
9444     }
9445     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9446                               moveList[forwardMostMove - 1]);
9447     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9448       case MT_NONE:
9449       case MT_STALEMATE:
9450       default:
9451         break;
9452       case MT_CHECK:
9453         if(gameInfo.variant != VariantShogi)
9454             strcat(parseList[forwardMostMove - 1], "+");
9455         break;
9456       case MT_CHECKMATE:
9457       case MT_STAINMATE:
9458         strcat(parseList[forwardMostMove - 1], "#");
9459         break;
9460     }
9461     if (appData.debugMode) {
9462         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9463     }
9464
9465 }
9466
9467 /* Updates currentMove if not pausing */
9468 void
9469 ShowMove(fromX, fromY, toX, toY)
9470 {
9471     int instant = (gameMode == PlayFromGameFile) ?
9472         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9473     if(appData.noGUI) return;
9474     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9475         if (!instant) {
9476             if (forwardMostMove == currentMove + 1) {
9477                 AnimateMove(boards[forwardMostMove - 1],
9478                             fromX, fromY, toX, toY);
9479             }
9480             if (appData.highlightLastMove) {
9481                 SetHighlights(fromX, fromY, toX, toY);
9482             }
9483         }
9484         currentMove = forwardMostMove;
9485     }
9486
9487     if (instant) return;
9488
9489     DisplayMove(currentMove - 1);
9490     DrawPosition(FALSE, boards[currentMove]);
9491     DisplayBothClocks();
9492     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9493 }
9494
9495 void SendEgtPath(ChessProgramState *cps)
9496 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9497         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9498
9499         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9500
9501         while(*p) {
9502             char c, *q = name+1, *r, *s;
9503
9504             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9505             while(*p && *p != ',') *q++ = *p++;
9506             *q++ = ':'; *q = 0;
9507             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9508                 strcmp(name, ",nalimov:") == 0 ) {
9509                 // take nalimov path from the menu-changeable option first, if it is defined
9510               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9511                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9512             } else
9513             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9514                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9515                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9516                 s = r = StrStr(s, ":") + 1; // beginning of path info
9517                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9518                 c = *r; *r = 0;             // temporarily null-terminate path info
9519                     *--q = 0;               // strip of trailig ':' from name
9520                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9521                 *r = c;
9522                 SendToProgram(buf,cps);     // send egtbpath command for this format
9523             }
9524             if(*p == ',') p++; // read away comma to position for next format name
9525         }
9526 }
9527
9528 void
9529 InitChessProgram(cps, setup)
9530      ChessProgramState *cps;
9531      int setup; /* [HGM] needed to setup FRC opening position */
9532 {
9533     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9534     if (appData.noChessProgram) return;
9535     hintRequested = FALSE;
9536     bookRequested = FALSE;
9537
9538     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9539     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9540     if(cps->memSize) { /* [HGM] memory */
9541       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9542         SendToProgram(buf, cps);
9543     }
9544     SendEgtPath(cps); /* [HGM] EGT */
9545     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9546       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9547         SendToProgram(buf, cps);
9548     }
9549
9550     SendToProgram(cps->initString, cps);
9551     if (gameInfo.variant != VariantNormal &&
9552         gameInfo.variant != VariantLoadable
9553         /* [HGM] also send variant if board size non-standard */
9554         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9555                                             ) {
9556       char *v = VariantName(gameInfo.variant);
9557       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9558         /* [HGM] in protocol 1 we have to assume all variants valid */
9559         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9560         DisplayFatalError(buf, 0, 1);
9561         return;
9562       }
9563
9564       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9565       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9566       if( gameInfo.variant == VariantXiangqi )
9567            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9568       if( gameInfo.variant == VariantShogi )
9569            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9570       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9571            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9572       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9573           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9574            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9575       if( gameInfo.variant == VariantCourier )
9576            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9577       if( gameInfo.variant == VariantSuper )
9578            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9579       if( gameInfo.variant == VariantGreat )
9580            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9581       if( gameInfo.variant == VariantSChess )
9582            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9583       if( gameInfo.variant == VariantGrand )
9584            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9585
9586       if(overruled) {
9587         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9588                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9589            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9590            if(StrStr(cps->variants, b) == NULL) {
9591                // specific sized variant not known, check if general sizing allowed
9592                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9593                    if(StrStr(cps->variants, "boardsize") == NULL) {
9594                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9595                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9596                        DisplayFatalError(buf, 0, 1);
9597                        return;
9598                    }
9599                    /* [HGM] here we really should compare with the maximum supported board size */
9600                }
9601            }
9602       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9603       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9604       SendToProgram(buf, cps);
9605     }
9606     currentlyInitializedVariant = gameInfo.variant;
9607
9608     /* [HGM] send opening position in FRC to first engine */
9609     if(setup) {
9610           SendToProgram("force\n", cps);
9611           SendBoard(cps, 0);
9612           /* engine is now in force mode! Set flag to wake it up after first move. */
9613           setboardSpoiledMachineBlack = 1;
9614     }
9615
9616     if (cps->sendICS) {
9617       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9618       SendToProgram(buf, cps);
9619     }
9620     cps->maybeThinking = FALSE;
9621     cps->offeredDraw = 0;
9622     if (!appData.icsActive) {
9623         SendTimeControl(cps, movesPerSession, timeControl,
9624                         timeIncrement, appData.searchDepth,
9625                         searchTime);
9626     }
9627     if (appData.showThinking
9628         // [HGM] thinking: four options require thinking output to be sent
9629         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9630                                 ) {
9631         SendToProgram("post\n", cps);
9632     }
9633     SendToProgram("hard\n", cps);
9634     if (!appData.ponderNextMove) {
9635         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9636            it without being sure what state we are in first.  "hard"
9637            is not a toggle, so that one is OK.
9638          */
9639         SendToProgram("easy\n", cps);
9640     }
9641     if (cps->usePing) {
9642       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9643       SendToProgram(buf, cps);
9644     }
9645     cps->initDone = TRUE;
9646     ClearEngineOutputPane(cps == &second);
9647 }
9648
9649
9650 void
9651 StartChessProgram(cps)
9652      ChessProgramState *cps;
9653 {
9654     char buf[MSG_SIZ];
9655     int err;
9656
9657     if (appData.noChessProgram) return;
9658     cps->initDone = FALSE;
9659
9660     if (strcmp(cps->host, "localhost") == 0) {
9661         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9662     } else if (*appData.remoteShell == NULLCHAR) {
9663         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9664     } else {
9665         if (*appData.remoteUser == NULLCHAR) {
9666           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9667                     cps->program);
9668         } else {
9669           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9670                     cps->host, appData.remoteUser, cps->program);
9671         }
9672         err = StartChildProcess(buf, "", &cps->pr);
9673     }
9674
9675     if (err != 0) {
9676       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9677         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9678         if(cps != &first) return;
9679         appData.noChessProgram = TRUE;
9680         ThawUI();
9681         SetNCPMode();
9682 //      DisplayFatalError(buf, err, 1);
9683 //      cps->pr = NoProc;
9684 //      cps->isr = NULL;
9685         return;
9686     }
9687
9688     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9689     if (cps->protocolVersion > 1) {
9690       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9691       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9692       cps->comboCnt = 0;  //                and values of combo boxes
9693       SendToProgram(buf, cps);
9694     } else {
9695       SendToProgram("xboard\n", cps);
9696     }
9697 }
9698
9699 void
9700 TwoMachinesEventIfReady P((void))
9701 {
9702   static int curMess = 0;
9703   if (first.lastPing != first.lastPong) {
9704     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9705     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9706     return;
9707   }
9708   if (second.lastPing != second.lastPong) {
9709     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9710     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9711     return;
9712   }
9713   DisplayMessage("", ""); curMess = 0;
9714   ThawUI();
9715   TwoMachinesEvent();
9716 }
9717
9718 char *
9719 MakeName(char *template)
9720 {
9721     time_t clock;
9722     struct tm *tm;
9723     static char buf[MSG_SIZ];
9724     char *p = buf;
9725     int i;
9726
9727     clock = time((time_t *)NULL);
9728     tm = localtime(&clock);
9729
9730     while(*p++ = *template++) if(p[-1] == '%') {
9731         switch(*template++) {
9732           case 0:   *p = 0; return buf;
9733           case 'Y': i = tm->tm_year+1900; break;
9734           case 'y': i = tm->tm_year-100; break;
9735           case 'M': i = tm->tm_mon+1; break;
9736           case 'd': i = tm->tm_mday; break;
9737           case 'h': i = tm->tm_hour; break;
9738           case 'm': i = tm->tm_min; break;
9739           case 's': i = tm->tm_sec; break;
9740           default:  i = 0;
9741         }
9742         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9743     }
9744     return buf;
9745 }
9746
9747 int
9748 CountPlayers(char *p)
9749 {
9750     int n = 0;
9751     while(p = strchr(p, '\n')) p++, n++; // count participants
9752     return n;
9753 }
9754
9755 FILE *
9756 WriteTourneyFile(char *results, FILE *f)
9757 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9758     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9759     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9760         // create a file with tournament description
9761         fprintf(f, "-participants {%s}\n", appData.participants);
9762         fprintf(f, "-seedBase %d\n", appData.seedBase);
9763         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9764         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9765         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9766         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9767         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9768         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9769         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9770         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9771         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9772         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9773         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9774         if(searchTime > 0)
9775                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9776         else {
9777                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9778                 fprintf(f, "-tc %s\n", appData.timeControl);
9779                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9780         }
9781         fprintf(f, "-results \"%s\"\n", results);
9782     }
9783     return f;
9784 }
9785
9786 #define MAXENGINES 1000
9787 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9788
9789 void Substitute(char *participants, int expunge)
9790 {
9791     int i, changed, changes=0, nPlayers=0;
9792     char *p, *q, *r, buf[MSG_SIZ];
9793     if(participants == NULL) return;
9794     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9795     r = p = participants; q = appData.participants;
9796     while(*p && *p == *q) {
9797         if(*p == '\n') r = p+1, nPlayers++;
9798         p++; q++;
9799     }
9800     if(*p) { // difference
9801         while(*p && *p++ != '\n');
9802         while(*q && *q++ != '\n');
9803       changed = nPlayers;
9804         changes = 1 + (strcmp(p, q) != 0);
9805     }
9806     if(changes == 1) { // a single engine mnemonic was changed
9807         q = r; while(*q) nPlayers += (*q++ == '\n');
9808         p = buf; while(*r && (*p = *r++) != '\n') p++;
9809         *p = NULLCHAR;
9810         NamesToList(firstChessProgramNames, command, mnemonic);
9811         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9812         if(mnemonic[i]) { // The substitute is valid
9813             FILE *f;
9814             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9815                 flock(fileno(f), LOCK_EX);
9816                 ParseArgsFromFile(f);
9817                 fseek(f, 0, SEEK_SET);
9818                 FREE(appData.participants); appData.participants = participants;
9819                 if(expunge) { // erase results of replaced engine
9820                     int len = strlen(appData.results), w, b, dummy;
9821                     for(i=0; i<len; i++) {
9822                         Pairing(i, nPlayers, &w, &b, &dummy);
9823                         if((w == changed || b == changed) && appData.results[i] == '*') {
9824                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9825                             fclose(f);
9826                             return;
9827                         }
9828                     }
9829                     for(i=0; i<len; i++) {
9830                         Pairing(i, nPlayers, &w, &b, &dummy);
9831                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9832                     }
9833                 }
9834                 WriteTourneyFile(appData.results, f);
9835                 fclose(f); // release lock
9836                 return;
9837             }
9838         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9839     }
9840     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9841     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9842     free(participants);
9843     return;
9844 }
9845
9846 int
9847 CreateTourney(char *name)
9848 {
9849         FILE *f;
9850         if(matchMode && strcmp(name, appData.tourneyFile)) {
9851              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9852         }
9853         if(name[0] == NULLCHAR) {
9854             if(appData.participants[0])
9855                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9856             return 0;
9857         }
9858         f = fopen(name, "r");
9859         if(f) { // file exists
9860             ASSIGN(appData.tourneyFile, name);
9861             ParseArgsFromFile(f); // parse it
9862         } else {
9863             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9864             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9865                 DisplayError(_("Not enough participants"), 0);
9866                 return 0;
9867             }
9868             ASSIGN(appData.tourneyFile, name);
9869             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9870             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9871         }
9872         fclose(f);
9873         appData.noChessProgram = FALSE;
9874         appData.clockMode = TRUE;
9875         SetGNUMode();
9876         return 1;
9877 }
9878
9879 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9880 {
9881     char buf[MSG_SIZ], *p, *q;
9882     int i=1;
9883     while(*names) {
9884         p = names; q = buf;
9885         while(*p && *p != '\n') *q++ = *p++;
9886         *q = 0;
9887         if(engineList[i]) free(engineList[i]);
9888         engineList[i] = strdup(buf);
9889         if(*p == '\n') p++;
9890         TidyProgramName(engineList[i], "localhost", buf);
9891         if(engineMnemonic[i]) free(engineMnemonic[i]);
9892         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9893             strcat(buf, " (");
9894             sscanf(q + 8, "%s", buf + strlen(buf));
9895             strcat(buf, ")");
9896         }
9897         engineMnemonic[i] = strdup(buf);
9898         names = p; i++;
9899       if(i > MAXENGINES - 2) break;
9900     }
9901     engineList[i] = engineMnemonic[i] = NULL;
9902 }
9903
9904 // following implemented as macro to avoid type limitations
9905 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9906
9907 void SwapEngines(int n)
9908 {   // swap settings for first engine and other engine (so far only some selected options)
9909     int h;
9910     char *p;
9911     if(n == 0) return;
9912     SWAP(directory, p)
9913     SWAP(chessProgram, p)
9914     SWAP(isUCI, h)
9915     SWAP(hasOwnBookUCI, h)
9916     SWAP(protocolVersion, h)
9917     SWAP(reuse, h)
9918     SWAP(scoreIsAbsolute, h)
9919     SWAP(timeOdds, h)
9920     SWAP(logo, p)
9921     SWAP(pgnName, p)
9922     SWAP(pvSAN, h)
9923 }
9924
9925 void
9926 SetPlayer(int player)
9927 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9928     int i;
9929     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9930     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9931     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9932     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9933     if(mnemonic[i]) {
9934         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9935         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9936         ParseArgsFromString(buf);
9937     }
9938     free(engineName);
9939 }
9940
9941 int
9942 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9943 {   // determine players from game number
9944     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9945
9946     if(appData.tourneyType == 0) {
9947         roundsPerCycle = (nPlayers - 1) | 1;
9948         pairingsPerRound = nPlayers / 2;
9949     } else if(appData.tourneyType > 0) {
9950         roundsPerCycle = nPlayers - appData.tourneyType;
9951         pairingsPerRound = appData.tourneyType;
9952     }
9953     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9954     gamesPerCycle = gamesPerRound * roundsPerCycle;
9955     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9956     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9957     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9958     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9959     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9960     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9961
9962     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9963     if(appData.roundSync) *syncInterval = gamesPerRound;
9964
9965     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9966
9967     if(appData.tourneyType == 0) {
9968         if(curPairing == (nPlayers-1)/2 ) {
9969             *whitePlayer = curRound;
9970             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9971         } else {
9972             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9973             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9974             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9975             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9976         }
9977     } else if(appData.tourneyType > 0) {
9978         *whitePlayer = curPairing;
9979         *blackPlayer = curRound + appData.tourneyType;
9980     }
9981
9982     // take care of white/black alternation per round. 
9983     // For cycles and games this is already taken care of by default, derived from matchGame!
9984     return curRound & 1;
9985 }
9986
9987 int
9988 NextTourneyGame(int nr, int *swapColors)
9989 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9990     char *p, *q;
9991     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9992     FILE *tf;
9993     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9994     tf = fopen(appData.tourneyFile, "r");
9995     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9996     ParseArgsFromFile(tf); fclose(tf);
9997     InitTimeControls(); // TC might be altered from tourney file
9998
9999     nPlayers = CountPlayers(appData.participants); // count participants
10000     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10001     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10002
10003     if(syncInterval) {
10004         p = q = appData.results;
10005         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10006         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10007             DisplayMessage(_("Waiting for other game(s)"),"");
10008             waitingForGame = TRUE;
10009             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10010             return 0;
10011         }
10012         waitingForGame = FALSE;
10013     }
10014
10015     if(appData.tourneyType < 0) {
10016         if(nr>=0 && !pairingReceived) {
10017             char buf[1<<16];
10018             if(pairing.pr == NoProc) {
10019                 if(!appData.pairingEngine[0]) {
10020                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10021                     return 0;
10022                 }
10023                 StartChessProgram(&pairing); // starts the pairing engine
10024             }
10025             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10026             SendToProgram(buf, &pairing);
10027             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10028             SendToProgram(buf, &pairing);
10029             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10030         }
10031         pairingReceived = 0;                              // ... so we continue here 
10032         *swapColors = 0;
10033         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10034         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10035         matchGame = 1; roundNr = nr / syncInterval + 1;
10036     }
10037
10038     if(first.pr != NoProc) return 1; // engines already loaded
10039
10040     // redefine engines, engine dir, etc.
10041     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10042     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10043     SwapEngines(1);
10044     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10045     SwapEngines(1);         // and make that valid for second engine by swapping
10046     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10047     InitEngine(&second, 1);
10048     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10049     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10050     return 1;
10051 }
10052
10053 void
10054 NextMatchGame()
10055 {   // performs game initialization that does not invoke engines, and then tries to start the game
10056     int res, firstWhite, swapColors = 0;
10057     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10058     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10059     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10060     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10061     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10062     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10063     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10064     Reset(FALSE, first.pr != NoProc);
10065     res = LoadGameOrPosition(matchGame); // setup game
10066     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10067     if(!res) return; // abort when bad game/pos file
10068     TwoMachinesEvent();
10069 }
10070
10071 void UserAdjudicationEvent( int result )
10072 {
10073     ChessMove gameResult = GameIsDrawn;
10074
10075     if( result > 0 ) {
10076         gameResult = WhiteWins;
10077     }
10078     else if( result < 0 ) {
10079         gameResult = BlackWins;
10080     }
10081
10082     if( gameMode == TwoMachinesPlay ) {
10083         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10084     }
10085 }
10086
10087
10088 // [HGM] save: calculate checksum of game to make games easily identifiable
10089 int StringCheckSum(char *s)
10090 {
10091         int i = 0;
10092         if(s==NULL) return 0;
10093         while(*s) i = i*259 + *s++;
10094         return i;
10095 }
10096
10097 int GameCheckSum()
10098 {
10099         int i, sum=0;
10100         for(i=backwardMostMove; i<forwardMostMove; i++) {
10101                 sum += pvInfoList[i].depth;
10102                 sum += StringCheckSum(parseList[i]);
10103                 sum += StringCheckSum(commentList[i]);
10104                 sum *= 261;
10105         }
10106         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10107         return sum + StringCheckSum(commentList[i]);
10108 } // end of save patch
10109
10110 void
10111 GameEnds(result, resultDetails, whosays)
10112      ChessMove result;
10113      char *resultDetails;
10114      int whosays;
10115 {
10116     GameMode nextGameMode;
10117     int isIcsGame;
10118     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10119
10120     if(endingGame) return; /* [HGM] crash: forbid recursion */
10121     endingGame = 1;
10122     if(twoBoards) { // [HGM] dual: switch back to one board
10123         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10124         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10125     }
10126     if (appData.debugMode) {
10127       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10128               result, resultDetails ? resultDetails : "(null)", whosays);
10129     }
10130
10131     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10132
10133     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10134         /* If we are playing on ICS, the server decides when the
10135            game is over, but the engine can offer to draw, claim
10136            a draw, or resign.
10137          */
10138 #if ZIPPY
10139         if (appData.zippyPlay && first.initDone) {
10140             if (result == GameIsDrawn) {
10141                 /* In case draw still needs to be claimed */
10142                 SendToICS(ics_prefix);
10143                 SendToICS("draw\n");
10144             } else if (StrCaseStr(resultDetails, "resign")) {
10145                 SendToICS(ics_prefix);
10146                 SendToICS("resign\n");
10147             }
10148         }
10149 #endif
10150         endingGame = 0; /* [HGM] crash */
10151         return;
10152     }
10153
10154     /* If we're loading the game from a file, stop */
10155     if (whosays == GE_FILE) {
10156       (void) StopLoadGameTimer();
10157       gameFileFP = NULL;
10158     }
10159
10160     /* Cancel draw offers */
10161     first.offeredDraw = second.offeredDraw = 0;
10162
10163     /* If this is an ICS game, only ICS can really say it's done;
10164        if not, anyone can. */
10165     isIcsGame = (gameMode == IcsPlayingWhite ||
10166                  gameMode == IcsPlayingBlack ||
10167                  gameMode == IcsObserving    ||
10168                  gameMode == IcsExamining);
10169
10170     if (!isIcsGame || whosays == GE_ICS) {
10171         /* OK -- not an ICS game, or ICS said it was done */
10172         StopClocks();
10173         if (!isIcsGame && !appData.noChessProgram)
10174           SetUserThinkingEnables();
10175
10176         /* [HGM] if a machine claims the game end we verify this claim */
10177         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10178             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10179                 char claimer;
10180                 ChessMove trueResult = (ChessMove) -1;
10181
10182                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10183                                             first.twoMachinesColor[0] :
10184                                             second.twoMachinesColor[0] ;
10185
10186                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10187                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10188                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10189                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10190                 } else
10191                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10192                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10193                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10194                 } else
10195                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10196                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10197                 }
10198
10199                 // now verify win claims, but not in drop games, as we don't understand those yet
10200                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10201                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10202                     (result == WhiteWins && claimer == 'w' ||
10203                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10204                       if (appData.debugMode) {
10205                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10206                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10207                       }
10208                       if(result != trueResult) {
10209                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10210                               result = claimer == 'w' ? BlackWins : WhiteWins;
10211                               resultDetails = buf;
10212                       }
10213                 } else
10214                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10215                     && (forwardMostMove <= backwardMostMove ||
10216                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10217                         (claimer=='b')==(forwardMostMove&1))
10218                                                                                   ) {
10219                       /* [HGM] verify: draws that were not flagged are false claims */
10220                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10221                       result = claimer == 'w' ? BlackWins : WhiteWins;
10222                       resultDetails = buf;
10223                 }
10224                 /* (Claiming a loss is accepted no questions asked!) */
10225             }
10226             /* [HGM] bare: don't allow bare King to win */
10227             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10228                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10229                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10230                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10231                && result != GameIsDrawn)
10232             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10233                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10234                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10235                         if(p >= 0 && p <= (int)WhiteKing) k++;
10236                 }
10237                 if (appData.debugMode) {
10238                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10239                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10240                 }
10241                 if(k <= 1) {
10242                         result = GameIsDrawn;
10243                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10244                         resultDetails = buf;
10245                 }
10246             }
10247         }
10248
10249
10250         if(serverMoves != NULL && !loadFlag) { char c = '=';
10251             if(result==WhiteWins) c = '+';
10252             if(result==BlackWins) c = '-';
10253             if(resultDetails != NULL)
10254                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10255         }
10256         if (resultDetails != NULL) {
10257             gameInfo.result = result;
10258             gameInfo.resultDetails = StrSave(resultDetails);
10259
10260             /* display last move only if game was not loaded from file */
10261             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10262                 DisplayMove(currentMove - 1);
10263
10264             if (forwardMostMove != 0) {
10265                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10266                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10267                                                                 ) {
10268                     if (*appData.saveGameFile != NULLCHAR) {
10269                         SaveGameToFile(appData.saveGameFile, TRUE);
10270                     } else if (appData.autoSaveGames) {
10271                         AutoSaveGame();
10272                     }
10273                     if (*appData.savePositionFile != NULLCHAR) {
10274                         SavePositionToFile(appData.savePositionFile);
10275                     }
10276                 }
10277             }
10278
10279             /* Tell program how game ended in case it is learning */
10280             /* [HGM] Moved this to after saving the PGN, just in case */
10281             /* engine died and we got here through time loss. In that */
10282             /* case we will get a fatal error writing the pipe, which */
10283             /* would otherwise lose us the PGN.                       */
10284             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10285             /* output during GameEnds should never be fatal anymore   */
10286             if (gameMode == MachinePlaysWhite ||
10287                 gameMode == MachinePlaysBlack ||
10288                 gameMode == TwoMachinesPlay ||
10289                 gameMode == IcsPlayingWhite ||
10290                 gameMode == IcsPlayingBlack ||
10291                 gameMode == BeginningOfGame) {
10292                 char buf[MSG_SIZ];
10293                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10294                         resultDetails);
10295                 if (first.pr != NoProc) {
10296                     SendToProgram(buf, &first);
10297                 }
10298                 if (second.pr != NoProc &&
10299                     gameMode == TwoMachinesPlay) {
10300                     SendToProgram(buf, &second);
10301                 }
10302             }
10303         }
10304
10305         if (appData.icsActive) {
10306             if (appData.quietPlay &&
10307                 (gameMode == IcsPlayingWhite ||
10308                  gameMode == IcsPlayingBlack)) {
10309                 SendToICS(ics_prefix);
10310                 SendToICS("set shout 1\n");
10311             }
10312             nextGameMode = IcsIdle;
10313             ics_user_moved = FALSE;
10314             /* clean up premove.  It's ugly when the game has ended and the
10315              * premove highlights are still on the board.
10316              */
10317             if (gotPremove) {
10318               gotPremove = FALSE;
10319               ClearPremoveHighlights();
10320               DrawPosition(FALSE, boards[currentMove]);
10321             }
10322             if (whosays == GE_ICS) {
10323                 switch (result) {
10324                 case WhiteWins:
10325                     if (gameMode == IcsPlayingWhite)
10326                         PlayIcsWinSound();
10327                     else if(gameMode == IcsPlayingBlack)
10328                         PlayIcsLossSound();
10329                     break;
10330                 case BlackWins:
10331                     if (gameMode == IcsPlayingBlack)
10332                         PlayIcsWinSound();
10333                     else if(gameMode == IcsPlayingWhite)
10334                         PlayIcsLossSound();
10335                     break;
10336                 case GameIsDrawn:
10337                     PlayIcsDrawSound();
10338                     break;
10339                 default:
10340                     PlayIcsUnfinishedSound();
10341                 }
10342             }
10343         } else if (gameMode == EditGame ||
10344                    gameMode == PlayFromGameFile ||
10345                    gameMode == AnalyzeMode ||
10346                    gameMode == AnalyzeFile) {
10347             nextGameMode = gameMode;
10348         } else {
10349             nextGameMode = EndOfGame;
10350         }
10351         pausing = FALSE;
10352         ModeHighlight();
10353     } else {
10354         nextGameMode = gameMode;
10355     }
10356
10357     if (appData.noChessProgram) {
10358         gameMode = nextGameMode;
10359         ModeHighlight();
10360         endingGame = 0; /* [HGM] crash */
10361         return;
10362     }
10363
10364     if (first.reuse) {
10365         /* Put first chess program into idle state */
10366         if (first.pr != NoProc &&
10367             (gameMode == MachinePlaysWhite ||
10368              gameMode == MachinePlaysBlack ||
10369              gameMode == TwoMachinesPlay ||
10370              gameMode == IcsPlayingWhite ||
10371              gameMode == IcsPlayingBlack ||
10372              gameMode == BeginningOfGame)) {
10373             SendToProgram("force\n", &first);
10374             if (first.usePing) {
10375               char buf[MSG_SIZ];
10376               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10377               SendToProgram(buf, &first);
10378             }
10379         }
10380     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10381         /* Kill off first chess program */
10382         if (first.isr != NULL)
10383           RemoveInputSource(first.isr);
10384         first.isr = NULL;
10385
10386         if (first.pr != NoProc) {
10387             ExitAnalyzeMode();
10388             DoSleep( appData.delayBeforeQuit );
10389             SendToProgram("quit\n", &first);
10390             DoSleep( appData.delayAfterQuit );
10391             DestroyChildProcess(first.pr, first.useSigterm);
10392         }
10393         first.pr = NoProc;
10394     }
10395     if (second.reuse) {
10396         /* Put second chess program into idle state */
10397         if (second.pr != NoProc &&
10398             gameMode == TwoMachinesPlay) {
10399             SendToProgram("force\n", &second);
10400             if (second.usePing) {
10401               char buf[MSG_SIZ];
10402               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10403               SendToProgram(buf, &second);
10404             }
10405         }
10406     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10407         /* Kill off second chess program */
10408         if (second.isr != NULL)
10409           RemoveInputSource(second.isr);
10410         second.isr = NULL;
10411
10412         if (second.pr != NoProc) {
10413             DoSleep( appData.delayBeforeQuit );
10414             SendToProgram("quit\n", &second);
10415             DoSleep( appData.delayAfterQuit );
10416             DestroyChildProcess(second.pr, second.useSigterm);
10417         }
10418         second.pr = NoProc;
10419     }
10420
10421     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10422         char resChar = '=';
10423         switch (result) {
10424         case WhiteWins:
10425           resChar = '+';
10426           if (first.twoMachinesColor[0] == 'w') {
10427             first.matchWins++;
10428           } else {
10429             second.matchWins++;
10430           }
10431           break;
10432         case BlackWins:
10433           resChar = '-';
10434           if (first.twoMachinesColor[0] == 'b') {
10435             first.matchWins++;
10436           } else {
10437             second.matchWins++;
10438           }
10439           break;
10440         case GameUnfinished:
10441           resChar = ' ';
10442         default:
10443           break;
10444         }
10445
10446         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10447         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10448             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10449             ReserveGame(nextGame, resChar); // sets nextGame
10450             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10451             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10452         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10453
10454         if (nextGame <= appData.matchGames && !abortMatch) {
10455             gameMode = nextGameMode;
10456             matchGame = nextGame; // this will be overruled in tourney mode!
10457             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10458             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10459             endingGame = 0; /* [HGM] crash */
10460             return;
10461         } else {
10462             gameMode = nextGameMode;
10463             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10464                      first.tidy, second.tidy,
10465                      first.matchWins, second.matchWins,
10466                      appData.matchGames - (first.matchWins + second.matchWins));
10467             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10468             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10469             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10470                 first.twoMachinesColor = "black\n";
10471                 second.twoMachinesColor = "white\n";
10472             } else {
10473                 first.twoMachinesColor = "white\n";
10474                 second.twoMachinesColor = "black\n";
10475             }
10476         }
10477     }
10478     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10479         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10480       ExitAnalyzeMode();
10481     gameMode = nextGameMode;
10482     ModeHighlight();
10483     endingGame = 0;  /* [HGM] crash */
10484     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10485         if(matchMode == TRUE) { // match through command line: exit with or without popup
10486             if(ranking) {
10487                 ToNrEvent(forwardMostMove);
10488                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10489                 else ExitEvent(0);
10490             } else DisplayFatalError(buf, 0, 0);
10491         } else { // match through menu; just stop, with or without popup
10492             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10493             ModeHighlight();
10494             if(ranking){
10495                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10496             } else DisplayNote(buf);
10497       }
10498       if(ranking) free(ranking);
10499     }
10500 }
10501
10502 /* Assumes program was just initialized (initString sent).
10503    Leaves program in force mode. */
10504 void
10505 FeedMovesToProgram(cps, upto)
10506      ChessProgramState *cps;
10507      int upto;
10508 {
10509     int i;
10510
10511     if (appData.debugMode)
10512       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10513               startedFromSetupPosition ? "position and " : "",
10514               backwardMostMove, upto, cps->which);
10515     if(currentlyInitializedVariant != gameInfo.variant) {
10516       char buf[MSG_SIZ];
10517         // [HGM] variantswitch: make engine aware of new variant
10518         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10519                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10520         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10521         SendToProgram(buf, cps);
10522         currentlyInitializedVariant = gameInfo.variant;
10523     }
10524     SendToProgram("force\n", cps);
10525     if (startedFromSetupPosition) {
10526         SendBoard(cps, backwardMostMove);
10527     if (appData.debugMode) {
10528         fprintf(debugFP, "feedMoves\n");
10529     }
10530     }
10531     for (i = backwardMostMove; i < upto; i++) {
10532         SendMoveToProgram(i, cps);
10533     }
10534 }
10535
10536
10537 int
10538 ResurrectChessProgram()
10539 {
10540      /* The chess program may have exited.
10541         If so, restart it and feed it all the moves made so far. */
10542     static int doInit = 0;
10543
10544     if (appData.noChessProgram) return 1;
10545
10546     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10547         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10548         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10549         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10550     } else {
10551         if (first.pr != NoProc) return 1;
10552         StartChessProgram(&first);
10553     }
10554     InitChessProgram(&first, FALSE);
10555     FeedMovesToProgram(&first, currentMove);
10556
10557     if (!first.sendTime) {
10558         /* can't tell gnuchess what its clock should read,
10559            so we bow to its notion. */
10560         ResetClocks();
10561         timeRemaining[0][currentMove] = whiteTimeRemaining;
10562         timeRemaining[1][currentMove] = blackTimeRemaining;
10563     }
10564
10565     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10566                 appData.icsEngineAnalyze) && first.analysisSupport) {
10567       SendToProgram("analyze\n", &first);
10568       first.analyzing = TRUE;
10569     }
10570     return 1;
10571 }
10572
10573 /*
10574  * Button procedures
10575  */
10576 void
10577 Reset(redraw, init)
10578      int redraw, init;
10579 {
10580     int i;
10581
10582     if (appData.debugMode) {
10583         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10584                 redraw, init, gameMode);
10585     }
10586     CleanupTail(); // [HGM] vari: delete any stored variations
10587     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10588     pausing = pauseExamInvalid = FALSE;
10589     startedFromSetupPosition = blackPlaysFirst = FALSE;
10590     firstMove = TRUE;
10591     whiteFlag = blackFlag = FALSE;
10592     userOfferedDraw = FALSE;
10593     hintRequested = bookRequested = FALSE;
10594     first.maybeThinking = FALSE;
10595     second.maybeThinking = FALSE;
10596     first.bookSuspend = FALSE; // [HGM] book
10597     second.bookSuspend = FALSE;
10598     thinkOutput[0] = NULLCHAR;
10599     lastHint[0] = NULLCHAR;
10600     ClearGameInfo(&gameInfo);
10601     gameInfo.variant = StringToVariant(appData.variant);
10602     ics_user_moved = ics_clock_paused = FALSE;
10603     ics_getting_history = H_FALSE;
10604     ics_gamenum = -1;
10605     white_holding[0] = black_holding[0] = NULLCHAR;
10606     ClearProgramStats();
10607     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10608
10609     ResetFrontEnd();
10610     ClearHighlights();
10611     flipView = appData.flipView;
10612     ClearPremoveHighlights();
10613     gotPremove = FALSE;
10614     alarmSounded = FALSE;
10615
10616     GameEnds(EndOfFile, NULL, GE_PLAYER);
10617     if(appData.serverMovesName != NULL) {
10618         /* [HGM] prepare to make moves file for broadcasting */
10619         clock_t t = clock();
10620         if(serverMoves != NULL) fclose(serverMoves);
10621         serverMoves = fopen(appData.serverMovesName, "r");
10622         if(serverMoves != NULL) {
10623             fclose(serverMoves);
10624             /* delay 15 sec before overwriting, so all clients can see end */
10625             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10626         }
10627         serverMoves = fopen(appData.serverMovesName, "w");
10628     }
10629
10630     ExitAnalyzeMode();
10631     gameMode = BeginningOfGame;
10632     ModeHighlight();
10633     if(appData.icsActive) gameInfo.variant = VariantNormal;
10634     currentMove = forwardMostMove = backwardMostMove = 0;
10635     InitPosition(redraw);
10636     for (i = 0; i < MAX_MOVES; i++) {
10637         if (commentList[i] != NULL) {
10638             free(commentList[i]);
10639             commentList[i] = NULL;
10640         }
10641     }
10642     ResetClocks();
10643     timeRemaining[0][0] = whiteTimeRemaining;
10644     timeRemaining[1][0] = blackTimeRemaining;
10645
10646     if (first.pr == NULL) {
10647         StartChessProgram(&first);
10648     }
10649     if (init) {
10650             InitChessProgram(&first, startedFromSetupPosition);
10651     }
10652     DisplayTitle("");
10653     DisplayMessage("", "");
10654     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10655     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10656 }
10657
10658 void
10659 AutoPlayGameLoop()
10660 {
10661     for (;;) {
10662         if (!AutoPlayOneMove())
10663           return;
10664         if (matchMode || appData.timeDelay == 0)
10665           continue;
10666         if (appData.timeDelay < 0)
10667           return;
10668         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10669         break;
10670     }
10671 }
10672
10673
10674 int
10675 AutoPlayOneMove()
10676 {
10677     int fromX, fromY, toX, toY;
10678
10679     if (appData.debugMode) {
10680       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10681     }
10682
10683     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10684       return FALSE;
10685
10686     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10687       pvInfoList[currentMove].depth = programStats.depth;
10688       pvInfoList[currentMove].score = programStats.score;
10689       pvInfoList[currentMove].time  = 0;
10690       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10691     }
10692
10693     if (currentMove >= forwardMostMove) {
10694       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10695 //      gameMode = EndOfGame;
10696 //      ModeHighlight();
10697
10698       /* [AS] Clear current move marker at the end of a game */
10699       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10700
10701       return FALSE;
10702     }
10703
10704     toX = moveList[currentMove][2] - AAA;
10705     toY = moveList[currentMove][3] - ONE;
10706
10707     if (moveList[currentMove][1] == '@') {
10708         if (appData.highlightLastMove) {
10709             SetHighlights(-1, -1, toX, toY);
10710         }
10711     } else {
10712         fromX = moveList[currentMove][0] - AAA;
10713         fromY = moveList[currentMove][1] - ONE;
10714
10715         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10716
10717         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10718
10719         if (appData.highlightLastMove) {
10720             SetHighlights(fromX, fromY, toX, toY);
10721         }
10722     }
10723     DisplayMove(currentMove);
10724     SendMoveToProgram(currentMove++, &first);
10725     DisplayBothClocks();
10726     DrawPosition(FALSE, boards[currentMove]);
10727     // [HGM] PV info: always display, routine tests if empty
10728     DisplayComment(currentMove - 1, commentList[currentMove]);
10729     return TRUE;
10730 }
10731
10732
10733 int
10734 LoadGameOneMove(readAhead)
10735      ChessMove readAhead;
10736 {
10737     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10738     char promoChar = NULLCHAR;
10739     ChessMove moveType;
10740     char move[MSG_SIZ];
10741     char *p, *q;
10742
10743     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10744         gameMode != AnalyzeMode && gameMode != Training) {
10745         gameFileFP = NULL;
10746         return FALSE;
10747     }
10748
10749     yyboardindex = forwardMostMove;
10750     if (readAhead != EndOfFile) {
10751       moveType = readAhead;
10752     } else {
10753       if (gameFileFP == NULL)
10754           return FALSE;
10755       moveType = (ChessMove) Myylex();
10756     }
10757
10758     done = FALSE;
10759     switch (moveType) {
10760       case Comment:
10761         if (appData.debugMode)
10762           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10763         p = yy_text;
10764
10765         /* append the comment but don't display it */
10766         AppendComment(currentMove, p, FALSE);
10767         return TRUE;
10768
10769       case WhiteCapturesEnPassant:
10770       case BlackCapturesEnPassant:
10771       case WhitePromotion:
10772       case BlackPromotion:
10773       case WhiteNonPromotion:
10774       case BlackNonPromotion:
10775       case NormalMove:
10776       case WhiteKingSideCastle:
10777       case WhiteQueenSideCastle:
10778       case BlackKingSideCastle:
10779       case BlackQueenSideCastle:
10780       case WhiteKingSideCastleWild:
10781       case WhiteQueenSideCastleWild:
10782       case BlackKingSideCastleWild:
10783       case BlackQueenSideCastleWild:
10784       /* PUSH Fabien */
10785       case WhiteHSideCastleFR:
10786       case WhiteASideCastleFR:
10787       case BlackHSideCastleFR:
10788       case BlackASideCastleFR:
10789       /* POP Fabien */
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10792         fromX = currentMoveString[0] - AAA;
10793         fromY = currentMoveString[1] - ONE;
10794         toX = currentMoveString[2] - AAA;
10795         toY = currentMoveString[3] - ONE;
10796         promoChar = currentMoveString[4];
10797         break;
10798
10799       case WhiteDrop:
10800       case BlackDrop:
10801         if (appData.debugMode)
10802           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10803         fromX = moveType == WhiteDrop ?
10804           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10805         (int) CharToPiece(ToLower(currentMoveString[0]));
10806         fromY = DROP_RANK;
10807         toX = currentMoveString[2] - AAA;
10808         toY = currentMoveString[3] - ONE;
10809         break;
10810
10811       case WhiteWins:
10812       case BlackWins:
10813       case GameIsDrawn:
10814       case GameUnfinished:
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10817         p = strchr(yy_text, '{');
10818         if (p == NULL) p = strchr(yy_text, '(');
10819         if (p == NULL) {
10820             p = yy_text;
10821             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10822         } else {
10823             q = strchr(p, *p == '{' ? '}' : ')');
10824             if (q != NULL) *q = NULLCHAR;
10825             p++;
10826         }
10827         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10828         GameEnds(moveType, p, GE_FILE);
10829         done = TRUE;
10830         if (cmailMsgLoaded) {
10831             ClearHighlights();
10832             flipView = WhiteOnMove(currentMove);
10833             if (moveType == GameUnfinished) flipView = !flipView;
10834             if (appData.debugMode)
10835               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10836         }
10837         break;
10838
10839       case EndOfFile:
10840         if (appData.debugMode)
10841           fprintf(debugFP, "Parser hit end of file\n");
10842         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10843           case MT_NONE:
10844           case MT_CHECK:
10845             break;
10846           case MT_CHECKMATE:
10847           case MT_STAINMATE:
10848             if (WhiteOnMove(currentMove)) {
10849                 GameEnds(BlackWins, "Black mates", GE_FILE);
10850             } else {
10851                 GameEnds(WhiteWins, "White mates", GE_FILE);
10852             }
10853             break;
10854           case MT_STALEMATE:
10855             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10856             break;
10857         }
10858         done = TRUE;
10859         break;
10860
10861       case MoveNumberOne:
10862         if (lastLoadGameStart == GNUChessGame) {
10863             /* GNUChessGames have numbers, but they aren't move numbers */
10864             if (appData.debugMode)
10865               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10866                       yy_text, (int) moveType);
10867             return LoadGameOneMove(EndOfFile); /* tail recursion */
10868         }
10869         /* else fall thru */
10870
10871       case XBoardGame:
10872       case GNUChessGame:
10873       case PGNTag:
10874         /* Reached start of next game in file */
10875         if (appData.debugMode)
10876           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10877         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10878           case MT_NONE:
10879           case MT_CHECK:
10880             break;
10881           case MT_CHECKMATE:
10882           case MT_STAINMATE:
10883             if (WhiteOnMove(currentMove)) {
10884                 GameEnds(BlackWins, "Black mates", GE_FILE);
10885             } else {
10886                 GameEnds(WhiteWins, "White mates", GE_FILE);
10887             }
10888             break;
10889           case MT_STALEMATE:
10890             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10891             break;
10892         }
10893         done = TRUE;
10894         break;
10895
10896       case PositionDiagram:     /* should not happen; ignore */
10897       case ElapsedTime:         /* ignore */
10898       case NAG:                 /* ignore */
10899         if (appData.debugMode)
10900           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10901                   yy_text, (int) moveType);
10902         return LoadGameOneMove(EndOfFile); /* tail recursion */
10903
10904       case IllegalMove:
10905         if (appData.testLegality) {
10906             if (appData.debugMode)
10907               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10908             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10909                     (forwardMostMove / 2) + 1,
10910                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10911             DisplayError(move, 0);
10912             done = TRUE;
10913         } else {
10914             if (appData.debugMode)
10915               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10916                       yy_text, currentMoveString);
10917             fromX = currentMoveString[0] - AAA;
10918             fromY = currentMoveString[1] - ONE;
10919             toX = currentMoveString[2] - AAA;
10920             toY = currentMoveString[3] - ONE;
10921             promoChar = currentMoveString[4];
10922         }
10923         break;
10924
10925       case AmbiguousMove:
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10928         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10929                 (forwardMostMove / 2) + 1,
10930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10931         DisplayError(move, 0);
10932         done = TRUE;
10933         break;
10934
10935       default:
10936       case ImpossibleMove:
10937         if (appData.debugMode)
10938           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10939         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10940                 (forwardMostMove / 2) + 1,
10941                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10942         DisplayError(move, 0);
10943         done = TRUE;
10944         break;
10945     }
10946
10947     if (done) {
10948         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10949             DrawPosition(FALSE, boards[currentMove]);
10950             DisplayBothClocks();
10951             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10952               DisplayComment(currentMove - 1, commentList[currentMove]);
10953         }
10954         (void) StopLoadGameTimer();
10955         gameFileFP = NULL;
10956         cmailOldMove = forwardMostMove;
10957         return FALSE;
10958     } else {
10959         /* currentMoveString is set as a side-effect of yylex */
10960
10961         thinkOutput[0] = NULLCHAR;
10962         MakeMove(fromX, fromY, toX, toY, promoChar);
10963         currentMove = forwardMostMove;
10964         return TRUE;
10965     }
10966 }
10967
10968 /* Load the nth game from the given file */
10969 int
10970 LoadGameFromFile(filename, n, title, useList)
10971      char *filename;
10972      int n;
10973      char *title;
10974      /*Boolean*/ int useList;
10975 {
10976     FILE *f;
10977     char buf[MSG_SIZ];
10978
10979     if (strcmp(filename, "-") == 0) {
10980         f = stdin;
10981         title = "stdin";
10982     } else {
10983         f = fopen(filename, "rb");
10984         if (f == NULL) {
10985           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10986             DisplayError(buf, errno);
10987             return FALSE;
10988         }
10989     }
10990     if (fseek(f, 0, 0) == -1) {
10991         /* f is not seekable; probably a pipe */
10992         useList = FALSE;
10993     }
10994     if (useList && n == 0) {
10995         int error = GameListBuild(f);
10996         if (error) {
10997             DisplayError(_("Cannot build game list"), error);
10998         } else if (!ListEmpty(&gameList) &&
10999                    ((ListGame *) gameList.tailPred)->number > 1) {
11000             GameListPopUp(f, title);
11001             return TRUE;
11002         }
11003         GameListDestroy();
11004         n = 1;
11005     }
11006     if (n == 0) n = 1;
11007     return LoadGame(f, n, title, FALSE);
11008 }
11009
11010
11011 void
11012 MakeRegisteredMove()
11013 {
11014     int fromX, fromY, toX, toY;
11015     char promoChar;
11016     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11017         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11018           case CMAIL_MOVE:
11019           case CMAIL_DRAW:
11020             if (appData.debugMode)
11021               fprintf(debugFP, "Restoring %s for game %d\n",
11022                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11023
11024             thinkOutput[0] = NULLCHAR;
11025             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11026             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11027             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11028             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11029             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11030             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11031             MakeMove(fromX, fromY, toX, toY, promoChar);
11032             ShowMove(fromX, fromY, toX, toY);
11033
11034             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11035               case MT_NONE:
11036               case MT_CHECK:
11037                 break;
11038
11039               case MT_CHECKMATE:
11040               case MT_STAINMATE:
11041                 if (WhiteOnMove(currentMove)) {
11042                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11043                 } else {
11044                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11045                 }
11046                 break;
11047
11048               case MT_STALEMATE:
11049                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11050                 break;
11051             }
11052
11053             break;
11054
11055           case CMAIL_RESIGN:
11056             if (WhiteOnMove(currentMove)) {
11057                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11058             } else {
11059                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11060             }
11061             break;
11062
11063           case CMAIL_ACCEPT:
11064             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11065             break;
11066
11067           default:
11068             break;
11069         }
11070     }
11071
11072     return;
11073 }
11074
11075 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11076 int
11077 CmailLoadGame(f, gameNumber, title, useList)
11078      FILE *f;
11079      int gameNumber;
11080      char *title;
11081      int useList;
11082 {
11083     int retVal;
11084
11085     if (gameNumber > nCmailGames) {
11086         DisplayError(_("No more games in this message"), 0);
11087         return FALSE;
11088     }
11089     if (f == lastLoadGameFP) {
11090         int offset = gameNumber - lastLoadGameNumber;
11091         if (offset == 0) {
11092             cmailMsg[0] = NULLCHAR;
11093             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11094                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11095                 nCmailMovesRegistered--;
11096             }
11097             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11098             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11099                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11100             }
11101         } else {
11102             if (! RegisterMove()) return FALSE;
11103         }
11104     }
11105
11106     retVal = LoadGame(f, gameNumber, title, useList);
11107
11108     /* Make move registered during previous look at this game, if any */
11109     MakeRegisteredMove();
11110
11111     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11112         commentList[currentMove]
11113           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11114         DisplayComment(currentMove - 1, commentList[currentMove]);
11115     }
11116
11117     return retVal;
11118 }
11119
11120 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11121 int
11122 ReloadGame(offset)
11123      int offset;
11124 {
11125     int gameNumber = lastLoadGameNumber + offset;
11126     if (lastLoadGameFP == NULL) {
11127         DisplayError(_("No game has been loaded yet"), 0);
11128         return FALSE;
11129     }
11130     if (gameNumber <= 0) {
11131         DisplayError(_("Can't back up any further"), 0);
11132         return FALSE;
11133     }
11134     if (cmailMsgLoaded) {
11135         return CmailLoadGame(lastLoadGameFP, gameNumber,
11136                              lastLoadGameTitle, lastLoadGameUseList);
11137     } else {
11138         return LoadGame(lastLoadGameFP, gameNumber,
11139                         lastLoadGameTitle, lastLoadGameUseList);
11140     }
11141 }
11142
11143 int keys[EmptySquare+1];
11144
11145 int
11146 PositionMatches(Board b1, Board b2)
11147 {
11148     int r, f, sum=0;
11149     switch(appData.searchMode) {
11150         case 1: return CompareWithRights(b1, b2);
11151         case 2:
11152             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11153                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11154             }
11155             return TRUE;
11156         case 3:
11157             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11158               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11159                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11160             }
11161             return sum==0;
11162         case 4:
11163             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11164                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11165             }
11166             return sum==0;
11167     }
11168     return TRUE;
11169 }
11170
11171 GameInfo dummyInfo;
11172
11173 int GameContainsPosition(FILE *f, ListGame *lg)
11174 {
11175     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11176     int fromX, fromY, toX, toY;
11177     char promoChar;
11178     static int initDone=FALSE;
11179
11180     if(!initDone) {
11181         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11182         initDone = TRUE;
11183     }
11184     dummyInfo.variant = VariantNormal;
11185     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11186     dummyInfo.whiteRating = 0;
11187     dummyInfo.blackRating = 0;
11188     FREE(dummyInfo.date); dummyInfo.date = NULL;
11189     fseek(f, lg->offset, 0);
11190     yynewfile(f);
11191     CopyBoard(boards[scratch], initialPosition); // default start position
11192     while(1) {
11193         yyboardindex = scratch + (plyNr&1);
11194       quickFlag = 1;
11195         next = Myylex();
11196       quickFlag = 0;
11197         switch(next) {
11198             case PGNTag:
11199                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11200 #if 0
11201                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11202                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11203 #else
11204                 // do it ourselves avoiding malloc
11205                 { char *p = yy_text+1, *q;
11206                   while(!isdigit(*p) && !isalpha(*p)) p++;
11207                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11208                   *p = NULLCHAR;
11209                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11210                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11211                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11212                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11213                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11214                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11215                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11216                 }
11217 #endif
11218             default:
11219                 continue;
11220
11221             case XBoardGame:
11222             case GNUChessGame:
11223                 if(plyNr) return -1; // after we have seen moves, this is for new game
11224               continue;
11225
11226             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11227             case ImpossibleMove:
11228             case WhiteWins: // game ends here with these four
11229             case BlackWins:
11230             case GameIsDrawn:
11231             case GameUnfinished:
11232                 return -1;
11233
11234             case IllegalMove:
11235                 if(appData.testLegality) return -1;
11236             case WhiteCapturesEnPassant:
11237             case BlackCapturesEnPassant:
11238             case WhitePromotion:
11239             case BlackPromotion:
11240             case WhiteNonPromotion:
11241             case BlackNonPromotion:
11242             case NormalMove:
11243             case WhiteKingSideCastle:
11244             case WhiteQueenSideCastle:
11245             case BlackKingSideCastle:
11246             case BlackQueenSideCastle:
11247             case WhiteKingSideCastleWild:
11248             case WhiteQueenSideCastleWild:
11249             case BlackKingSideCastleWild:
11250             case BlackQueenSideCastleWild:
11251             case WhiteHSideCastleFR:
11252             case WhiteASideCastleFR:
11253             case BlackHSideCastleFR:
11254             case BlackASideCastleFR:
11255                 fromX = currentMoveString[0] - AAA;
11256                 fromY = currentMoveString[1] - ONE;
11257                 toX = currentMoveString[2] - AAA;
11258                 toY = currentMoveString[3] - ONE;
11259                 promoChar = currentMoveString[4];
11260                 break;
11261             case WhiteDrop:
11262             case BlackDrop:
11263                 fromX = next == WhiteDrop ?
11264                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11265                   (int) CharToPiece(ToLower(currentMoveString[0]));
11266                 fromY = DROP_RANK;
11267                 toX = currentMoveString[2] - AAA;
11268                 toY = currentMoveString[3] - ONE;
11269                 break;
11270         }
11271         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11272         if(plyNr == 0) { // but first figure out variant and initial position
11273             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11274             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11275             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11276             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11277             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11278             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11279         }
11280         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11281         plyNr++;
11282         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11283         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11284     }
11285 }
11286
11287 /* Load the nth game from open file f */
11288 int
11289 LoadGame(f, gameNumber, title, useList)
11290      FILE *f;
11291      int gameNumber;
11292      char *title;
11293      int useList;
11294 {
11295     ChessMove cm;
11296     char buf[MSG_SIZ];
11297     int gn = gameNumber;
11298     ListGame *lg = NULL;
11299     int numPGNTags = 0;
11300     int err, pos = -1;
11301     GameMode oldGameMode;
11302     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11303
11304     if (appData.debugMode)
11305         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11306
11307     if (gameMode == Training )
11308         SetTrainingModeOff();
11309
11310     oldGameMode = gameMode;
11311     if (gameMode != BeginningOfGame) {
11312       Reset(FALSE, TRUE);
11313     }
11314
11315     gameFileFP = f;
11316     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11317         fclose(lastLoadGameFP);
11318     }
11319
11320     if (useList) {
11321         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11322
11323         if (lg) {
11324             fseek(f, lg->offset, 0);
11325             GameListHighlight(gameNumber);
11326             pos = lg->position;
11327             gn = 1;
11328         }
11329         else {
11330             DisplayError(_("Game number out of range"), 0);
11331             return FALSE;
11332         }
11333     } else {
11334         GameListDestroy();
11335         if (fseek(f, 0, 0) == -1) {
11336             if (f == lastLoadGameFP ?
11337                 gameNumber == lastLoadGameNumber + 1 :
11338                 gameNumber == 1) {
11339                 gn = 1;
11340             } else {
11341                 DisplayError(_("Can't seek on game file"), 0);
11342                 return FALSE;
11343             }
11344         }
11345     }
11346     lastLoadGameFP = f;
11347     lastLoadGameNumber = gameNumber;
11348     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11349     lastLoadGameUseList = useList;
11350
11351     yynewfile(f);
11352
11353     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11354       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11355                 lg->gameInfo.black);
11356             DisplayTitle(buf);
11357     } else if (*title != NULLCHAR) {
11358         if (gameNumber > 1) {
11359           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11360             DisplayTitle(buf);
11361         } else {
11362             DisplayTitle(title);
11363         }
11364     }
11365
11366     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11367         gameMode = PlayFromGameFile;
11368         ModeHighlight();
11369     }
11370
11371     currentMove = forwardMostMove = backwardMostMove = 0;
11372     CopyBoard(boards[0], initialPosition);
11373     StopClocks();
11374
11375     /*
11376      * Skip the first gn-1 games in the file.
11377      * Also skip over anything that precedes an identifiable
11378      * start of game marker, to avoid being confused by
11379      * garbage at the start of the file.  Currently
11380      * recognized start of game markers are the move number "1",
11381      * the pattern "gnuchess .* game", the pattern
11382      * "^[#;%] [^ ]* game file", and a PGN tag block.
11383      * A game that starts with one of the latter two patterns
11384      * will also have a move number 1, possibly
11385      * following a position diagram.
11386      * 5-4-02: Let's try being more lenient and allowing a game to
11387      * start with an unnumbered move.  Does that break anything?
11388      */
11389     cm = lastLoadGameStart = EndOfFile;
11390     while (gn > 0) {
11391         yyboardindex = forwardMostMove;
11392         cm = (ChessMove) Myylex();
11393         switch (cm) {
11394           case EndOfFile:
11395             if (cmailMsgLoaded) {
11396                 nCmailGames = CMAIL_MAX_GAMES - gn;
11397             } else {
11398                 Reset(TRUE, TRUE);
11399                 DisplayError(_("Game not found in file"), 0);
11400             }
11401             return FALSE;
11402
11403           case GNUChessGame:
11404           case XBoardGame:
11405             gn--;
11406             lastLoadGameStart = cm;
11407             break;
11408
11409           case MoveNumberOne:
11410             switch (lastLoadGameStart) {
11411               case GNUChessGame:
11412               case XBoardGame:
11413               case PGNTag:
11414                 break;
11415               case MoveNumberOne:
11416               case EndOfFile:
11417                 gn--;           /* count this game */
11418                 lastLoadGameStart = cm;
11419                 break;
11420               default:
11421                 /* impossible */
11422                 break;
11423             }
11424             break;
11425
11426           case PGNTag:
11427             switch (lastLoadGameStart) {
11428               case GNUChessGame:
11429               case PGNTag:
11430               case MoveNumberOne:
11431               case EndOfFile:
11432                 gn--;           /* count this game */
11433                 lastLoadGameStart = cm;
11434                 break;
11435               case XBoardGame:
11436                 lastLoadGameStart = cm; /* game counted already */
11437                 break;
11438               default:
11439                 /* impossible */
11440                 break;
11441             }
11442             if (gn > 0) {
11443                 do {
11444                     yyboardindex = forwardMostMove;
11445                     cm = (ChessMove) Myylex();
11446                 } while (cm == PGNTag || cm == Comment);
11447             }
11448             break;
11449
11450           case WhiteWins:
11451           case BlackWins:
11452           case GameIsDrawn:
11453             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11454                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11455                     != CMAIL_OLD_RESULT) {
11456                     nCmailResults ++ ;
11457                     cmailResult[  CMAIL_MAX_GAMES
11458                                 - gn - 1] = CMAIL_OLD_RESULT;
11459                 }
11460             }
11461             break;
11462
11463           case NormalMove:
11464             /* Only a NormalMove can be at the start of a game
11465              * without a position diagram. */
11466             if (lastLoadGameStart == EndOfFile ) {
11467               gn--;
11468               lastLoadGameStart = MoveNumberOne;
11469             }
11470             break;
11471
11472           default:
11473             break;
11474         }
11475     }
11476
11477     if (appData.debugMode)
11478       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11479
11480     if (cm == XBoardGame) {
11481         /* Skip any header junk before position diagram and/or move 1 */
11482         for (;;) {
11483             yyboardindex = forwardMostMove;
11484             cm = (ChessMove) Myylex();
11485
11486             if (cm == EndOfFile ||
11487                 cm == GNUChessGame || cm == XBoardGame) {
11488                 /* Empty game; pretend end-of-file and handle later */
11489                 cm = EndOfFile;
11490                 break;
11491             }
11492
11493             if (cm == MoveNumberOne || cm == PositionDiagram ||
11494                 cm == PGNTag || cm == Comment)
11495               break;
11496         }
11497     } else if (cm == GNUChessGame) {
11498         if (gameInfo.event != NULL) {
11499             free(gameInfo.event);
11500         }
11501         gameInfo.event = StrSave(yy_text);
11502     }
11503
11504     startedFromSetupPosition = FALSE;
11505     while (cm == PGNTag) {
11506         if (appData.debugMode)
11507           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11508         err = ParsePGNTag(yy_text, &gameInfo);
11509         if (!err) numPGNTags++;
11510
11511         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11512         if(gameInfo.variant != oldVariant) {
11513             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11514             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11515             InitPosition(TRUE);
11516             oldVariant = gameInfo.variant;
11517             if (appData.debugMode)
11518               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11519         }
11520
11521
11522         if (gameInfo.fen != NULL) {
11523           Board initial_position;
11524           startedFromSetupPosition = TRUE;
11525           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11526             Reset(TRUE, TRUE);
11527             DisplayError(_("Bad FEN position in file"), 0);
11528             return FALSE;
11529           }
11530           CopyBoard(boards[0], initial_position);
11531           if (blackPlaysFirst) {
11532             currentMove = forwardMostMove = backwardMostMove = 1;
11533             CopyBoard(boards[1], initial_position);
11534             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11535             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11536             timeRemaining[0][1] = whiteTimeRemaining;
11537             timeRemaining[1][1] = blackTimeRemaining;
11538             if (commentList[0] != NULL) {
11539               commentList[1] = commentList[0];
11540               commentList[0] = NULL;
11541             }
11542           } else {
11543             currentMove = forwardMostMove = backwardMostMove = 0;
11544           }
11545           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11546           {   int i;
11547               initialRulePlies = FENrulePlies;
11548               for( i=0; i< nrCastlingRights; i++ )
11549                   initialRights[i] = initial_position[CASTLING][i];
11550           }
11551           yyboardindex = forwardMostMove;
11552           free(gameInfo.fen);
11553           gameInfo.fen = NULL;
11554         }
11555
11556         yyboardindex = forwardMostMove;
11557         cm = (ChessMove) Myylex();
11558
11559         /* Handle comments interspersed among the tags */
11560         while (cm == Comment) {
11561             char *p;
11562             if (appData.debugMode)
11563               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11564             p = yy_text;
11565             AppendComment(currentMove, p, FALSE);
11566             yyboardindex = forwardMostMove;
11567             cm = (ChessMove) Myylex();
11568         }
11569     }
11570
11571     /* don't rely on existence of Event tag since if game was
11572      * pasted from clipboard the Event tag may not exist
11573      */
11574     if (numPGNTags > 0){
11575         char *tags;
11576         if (gameInfo.variant == VariantNormal) {
11577           VariantClass v = StringToVariant(gameInfo.event);
11578           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11579           if(v < VariantShogi) gameInfo.variant = v;
11580         }
11581         if (!matchMode) {
11582           if( appData.autoDisplayTags ) {
11583             tags = PGNTags(&gameInfo);
11584             TagsPopUp(tags, CmailMsg());
11585             free(tags);
11586           }
11587         }
11588     } else {
11589         /* Make something up, but don't display it now */
11590         SetGameInfo();
11591         TagsPopDown();
11592     }
11593
11594     if (cm == PositionDiagram) {
11595         int i, j;
11596         char *p;
11597         Board initial_position;
11598
11599         if (appData.debugMode)
11600           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11601
11602         if (!startedFromSetupPosition) {
11603             p = yy_text;
11604             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11605               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11606                 switch (*p) {
11607                   case '{':
11608                   case '[':
11609                   case '-':
11610                   case ' ':
11611                   case '\t':
11612                   case '\n':
11613                   case '\r':
11614                     break;
11615                   default:
11616                     initial_position[i][j++] = CharToPiece(*p);
11617                     break;
11618                 }
11619             while (*p == ' ' || *p == '\t' ||
11620                    *p == '\n' || *p == '\r') p++;
11621
11622             if (strncmp(p, "black", strlen("black"))==0)
11623               blackPlaysFirst = TRUE;
11624             else
11625               blackPlaysFirst = FALSE;
11626             startedFromSetupPosition = TRUE;
11627
11628             CopyBoard(boards[0], initial_position);
11629             if (blackPlaysFirst) {
11630                 currentMove = forwardMostMove = backwardMostMove = 1;
11631                 CopyBoard(boards[1], initial_position);
11632                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11633                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11634                 timeRemaining[0][1] = whiteTimeRemaining;
11635                 timeRemaining[1][1] = blackTimeRemaining;
11636                 if (commentList[0] != NULL) {
11637                     commentList[1] = commentList[0];
11638                     commentList[0] = NULL;
11639                 }
11640             } else {
11641                 currentMove = forwardMostMove = backwardMostMove = 0;
11642             }
11643         }
11644         yyboardindex = forwardMostMove;
11645         cm = (ChessMove) Myylex();
11646     }
11647
11648     if (first.pr == NoProc) {
11649         StartChessProgram(&first);
11650     }
11651     InitChessProgram(&first, FALSE);
11652     SendToProgram("force\n", &first);
11653     if (startedFromSetupPosition) {
11654         SendBoard(&first, forwardMostMove);
11655     if (appData.debugMode) {
11656         fprintf(debugFP, "Load Game\n");
11657     }
11658         DisplayBothClocks();
11659     }
11660
11661     /* [HGM] server: flag to write setup moves in broadcast file as one */
11662     loadFlag = appData.suppressLoadMoves;
11663
11664     while (cm == Comment) {
11665         char *p;
11666         if (appData.debugMode)
11667           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11668         p = yy_text;
11669         AppendComment(currentMove, p, FALSE);
11670         yyboardindex = forwardMostMove;
11671         cm = (ChessMove) Myylex();
11672     }
11673
11674     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11675         cm == WhiteWins || cm == BlackWins ||
11676         cm == GameIsDrawn || cm == GameUnfinished) {
11677         DisplayMessage("", _("No moves in game"));
11678         if (cmailMsgLoaded) {
11679             if (appData.debugMode)
11680               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11681             ClearHighlights();
11682             flipView = FALSE;
11683         }
11684         DrawPosition(FALSE, boards[currentMove]);
11685         DisplayBothClocks();
11686         gameMode = EditGame;
11687         ModeHighlight();
11688         gameFileFP = NULL;
11689         cmailOldMove = 0;
11690         return TRUE;
11691     }
11692
11693     // [HGM] PV info: routine tests if comment empty
11694     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11695         DisplayComment(currentMove - 1, commentList[currentMove]);
11696     }
11697     if (!matchMode && appData.timeDelay != 0)
11698       DrawPosition(FALSE, boards[currentMove]);
11699
11700     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11701       programStats.ok_to_send = 1;
11702     }
11703
11704     /* if the first token after the PGN tags is a move
11705      * and not move number 1, retrieve it from the parser
11706      */
11707     if (cm != MoveNumberOne)
11708         LoadGameOneMove(cm);
11709
11710     /* load the remaining moves from the file */
11711     while (LoadGameOneMove(EndOfFile)) {
11712       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11713       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11714     }
11715
11716     /* rewind to the start of the game */
11717     currentMove = backwardMostMove;
11718
11719     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11720
11721     if (oldGameMode == AnalyzeFile ||
11722         oldGameMode == AnalyzeMode) {
11723       AnalyzeFileEvent();
11724     }
11725
11726     if (!matchMode && pos >= 0) {
11727         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11728     } else
11729     if (matchMode || appData.timeDelay == 0) {
11730       ToEndEvent();
11731     } else if (appData.timeDelay > 0) {
11732       AutoPlayGameLoop();
11733     }
11734
11735     if (appData.debugMode)
11736         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11737
11738     loadFlag = 0; /* [HGM] true game starts */
11739     return TRUE;
11740 }
11741
11742 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11743 int
11744 ReloadPosition(offset)
11745      int offset;
11746 {
11747     int positionNumber = lastLoadPositionNumber + offset;
11748     if (lastLoadPositionFP == NULL) {
11749         DisplayError(_("No position has been loaded yet"), 0);
11750         return FALSE;
11751     }
11752     if (positionNumber <= 0) {
11753         DisplayError(_("Can't back up any further"), 0);
11754         return FALSE;
11755     }
11756     return LoadPosition(lastLoadPositionFP, positionNumber,
11757                         lastLoadPositionTitle);
11758 }
11759
11760 /* Load the nth position from the given file */
11761 int
11762 LoadPositionFromFile(filename, n, title)
11763      char *filename;
11764      int n;
11765      char *title;
11766 {
11767     FILE *f;
11768     char buf[MSG_SIZ];
11769
11770     if (strcmp(filename, "-") == 0) {
11771         return LoadPosition(stdin, n, "stdin");
11772     } else {
11773         f = fopen(filename, "rb");
11774         if (f == NULL) {
11775             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11776             DisplayError(buf, errno);
11777             return FALSE;
11778         } else {
11779             return LoadPosition(f, n, title);
11780         }
11781     }
11782 }
11783
11784 /* Load the nth position from the given open file, and close it */
11785 int
11786 LoadPosition(f, positionNumber, title)
11787      FILE *f;
11788      int positionNumber;
11789      char *title;
11790 {
11791     char *p, line[MSG_SIZ];
11792     Board initial_position;
11793     int i, j, fenMode, pn;
11794
11795     if (gameMode == Training )
11796         SetTrainingModeOff();
11797
11798     if (gameMode != BeginningOfGame) {
11799         Reset(FALSE, TRUE);
11800     }
11801     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11802         fclose(lastLoadPositionFP);
11803     }
11804     if (positionNumber == 0) positionNumber = 1;
11805     lastLoadPositionFP = f;
11806     lastLoadPositionNumber = positionNumber;
11807     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11808     if (first.pr == NoProc && !appData.noChessProgram) {
11809       StartChessProgram(&first);
11810       InitChessProgram(&first, FALSE);
11811     }
11812     pn = positionNumber;
11813     if (positionNumber < 0) {
11814         /* Negative position number means to seek to that byte offset */
11815         if (fseek(f, -positionNumber, 0) == -1) {
11816             DisplayError(_("Can't seek on position file"), 0);
11817             return FALSE;
11818         };
11819         pn = 1;
11820     } else {
11821         if (fseek(f, 0, 0) == -1) {
11822             if (f == lastLoadPositionFP ?
11823                 positionNumber == lastLoadPositionNumber + 1 :
11824                 positionNumber == 1) {
11825                 pn = 1;
11826             } else {
11827                 DisplayError(_("Can't seek on position file"), 0);
11828                 return FALSE;
11829             }
11830         }
11831     }
11832     /* See if this file is FEN or old-style xboard */
11833     if (fgets(line, MSG_SIZ, f) == NULL) {
11834         DisplayError(_("Position not found in file"), 0);
11835         return FALSE;
11836     }
11837     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11838     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11839
11840     if (pn >= 2) {
11841         if (fenMode || line[0] == '#') pn--;
11842         while (pn > 0) {
11843             /* skip positions before number pn */
11844             if (fgets(line, MSG_SIZ, f) == NULL) {
11845                 Reset(TRUE, TRUE);
11846                 DisplayError(_("Position not found in file"), 0);
11847                 return FALSE;
11848             }
11849             if (fenMode || line[0] == '#') pn--;
11850         }
11851     }
11852
11853     if (fenMode) {
11854         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11855             DisplayError(_("Bad FEN position in file"), 0);
11856             return FALSE;
11857         }
11858     } else {
11859         (void) fgets(line, MSG_SIZ, f);
11860         (void) fgets(line, MSG_SIZ, f);
11861
11862         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11863             (void) fgets(line, MSG_SIZ, f);
11864             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11865                 if (*p == ' ')
11866                   continue;
11867                 initial_position[i][j++] = CharToPiece(*p);
11868             }
11869         }
11870
11871         blackPlaysFirst = FALSE;
11872         if (!feof(f)) {
11873             (void) fgets(line, MSG_SIZ, f);
11874             if (strncmp(line, "black", strlen("black"))==0)
11875               blackPlaysFirst = TRUE;
11876         }
11877     }
11878     startedFromSetupPosition = TRUE;
11879
11880     CopyBoard(boards[0], initial_position);
11881     if (blackPlaysFirst) {
11882         currentMove = forwardMostMove = backwardMostMove = 1;
11883         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11884         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11885         CopyBoard(boards[1], initial_position);
11886         DisplayMessage("", _("Black to play"));
11887     } else {
11888         currentMove = forwardMostMove = backwardMostMove = 0;
11889         DisplayMessage("", _("White to play"));
11890     }
11891     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11892     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
11893         SendToProgram("force\n", &first);
11894         SendBoard(&first, forwardMostMove);
11895     }
11896     if (appData.debugMode) {
11897 int i, j;
11898   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11899   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11900         fprintf(debugFP, "Load Position\n");
11901     }
11902
11903     if (positionNumber > 1) {
11904       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11905         DisplayTitle(line);
11906     } else {
11907         DisplayTitle(title);
11908     }
11909     gameMode = EditGame;
11910     ModeHighlight();
11911     ResetClocks();
11912     timeRemaining[0][1] = whiteTimeRemaining;
11913     timeRemaining[1][1] = blackTimeRemaining;
11914     DrawPosition(FALSE, boards[currentMove]);
11915
11916     return TRUE;
11917 }
11918
11919
11920 void
11921 CopyPlayerNameIntoFileName(dest, src)
11922      char **dest, *src;
11923 {
11924     while (*src != NULLCHAR && *src != ',') {
11925         if (*src == ' ') {
11926             *(*dest)++ = '_';
11927             src++;
11928         } else {
11929             *(*dest)++ = *src++;
11930         }
11931     }
11932 }
11933
11934 char *DefaultFileName(ext)
11935      char *ext;
11936 {
11937     static char def[MSG_SIZ];
11938     char *p;
11939
11940     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11941         p = def;
11942         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11943         *p++ = '-';
11944         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11945         *p++ = '.';
11946         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11947     } else {
11948         def[0] = NULLCHAR;
11949     }
11950     return def;
11951 }
11952
11953 /* Save the current game to the given file */
11954 int
11955 SaveGameToFile(filename, append)
11956      char *filename;
11957      int append;
11958 {
11959     FILE *f;
11960     char buf[MSG_SIZ];
11961     int result, i, t,tot=0;
11962
11963     if (strcmp(filename, "-") == 0) {
11964         return SaveGame(stdout, 0, NULL);
11965     } else {
11966         for(i=0; i<10; i++) { // upto 10 tries
11967              f = fopen(filename, append ? "a" : "w");
11968              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
11969              if(f || errno != 13) break;
11970              DoSleep(t = 5 + random()%11); // wait 5-15 msec
11971              tot += t;
11972         }
11973         if (f == NULL) {
11974             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11975             DisplayError(buf, errno);
11976             return FALSE;
11977         } else {
11978             safeStrCpy(buf, lastMsg, MSG_SIZ);
11979             DisplayMessage(_("Waiting for access to save file"), "");
11980             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11981             DisplayMessage(_("Saving game"), "");
11982             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11983             result = SaveGame(f, 0, NULL);
11984             DisplayMessage(buf, "");
11985             return result;
11986         }
11987     }
11988 }
11989
11990 char *
11991 SavePart(str)
11992      char *str;
11993 {
11994     static char buf[MSG_SIZ];
11995     char *p;
11996
11997     p = strchr(str, ' ');
11998     if (p == NULL) return str;
11999     strncpy(buf, str, p - str);
12000     buf[p - str] = NULLCHAR;
12001     return buf;
12002 }
12003
12004 #define PGN_MAX_LINE 75
12005
12006 #define PGN_SIDE_WHITE  0
12007 #define PGN_SIDE_BLACK  1
12008
12009 /* [AS] */
12010 static int FindFirstMoveOutOfBook( int side )
12011 {
12012     int result = -1;
12013
12014     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12015         int index = backwardMostMove;
12016         int has_book_hit = 0;
12017
12018         if( (index % 2) != side ) {
12019             index++;
12020         }
12021
12022         while( index < forwardMostMove ) {
12023             /* Check to see if engine is in book */
12024             int depth = pvInfoList[index].depth;
12025             int score = pvInfoList[index].score;
12026             int in_book = 0;
12027
12028             if( depth <= 2 ) {
12029                 in_book = 1;
12030             }
12031             else if( score == 0 && depth == 63 ) {
12032                 in_book = 1; /* Zappa */
12033             }
12034             else if( score == 2 && depth == 99 ) {
12035                 in_book = 1; /* Abrok */
12036             }
12037
12038             has_book_hit += in_book;
12039
12040             if( ! in_book ) {
12041                 result = index;
12042
12043                 break;
12044             }
12045
12046             index += 2;
12047         }
12048     }
12049
12050     return result;
12051 }
12052
12053 /* [AS] */
12054 void GetOutOfBookInfo( char * buf )
12055 {
12056     int oob[2];
12057     int i;
12058     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12059
12060     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12061     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12062
12063     *buf = '\0';
12064
12065     if( oob[0] >= 0 || oob[1] >= 0 ) {
12066         for( i=0; i<2; i++ ) {
12067             int idx = oob[i];
12068
12069             if( idx >= 0 ) {
12070                 if( i > 0 && oob[0] >= 0 ) {
12071                     strcat( buf, "   " );
12072                 }
12073
12074                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12075                 sprintf( buf+strlen(buf), "%s%.2f",
12076                     pvInfoList[idx].score >= 0 ? "+" : "",
12077                     pvInfoList[idx].score / 100.0 );
12078             }
12079         }
12080     }
12081 }
12082
12083 /* Save game in PGN style and close the file */
12084 int
12085 SaveGamePGN(f)
12086      FILE *f;
12087 {
12088     int i, offset, linelen, newblock;
12089     time_t tm;
12090 //    char *movetext;
12091     char numtext[32];
12092     int movelen, numlen, blank;
12093     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12094
12095     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12096
12097     tm = time((time_t *) NULL);
12098
12099     PrintPGNTags(f, &gameInfo);
12100
12101     if (backwardMostMove > 0 || startedFromSetupPosition) {
12102         char *fen = PositionToFEN(backwardMostMove, NULL);
12103         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12104         fprintf(f, "\n{--------------\n");
12105         PrintPosition(f, backwardMostMove);
12106         fprintf(f, "--------------}\n");
12107         free(fen);
12108     }
12109     else {
12110         /* [AS] Out of book annotation */
12111         if( appData.saveOutOfBookInfo ) {
12112             char buf[64];
12113
12114             GetOutOfBookInfo( buf );
12115
12116             if( buf[0] != '\0' ) {
12117                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12118             }
12119         }
12120
12121         fprintf(f, "\n");
12122     }
12123
12124     i = backwardMostMove;
12125     linelen = 0;
12126     newblock = TRUE;
12127
12128     while (i < forwardMostMove) {
12129         /* Print comments preceding this move */
12130         if (commentList[i] != NULL) {
12131             if (linelen > 0) fprintf(f, "\n");
12132             fprintf(f, "%s", commentList[i]);
12133             linelen = 0;
12134             newblock = TRUE;
12135         }
12136
12137         /* Format move number */
12138         if ((i % 2) == 0)
12139           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12140         else
12141           if (newblock)
12142             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12143           else
12144             numtext[0] = NULLCHAR;
12145
12146         numlen = strlen(numtext);
12147         newblock = FALSE;
12148
12149         /* Print move number */
12150         blank = linelen > 0 && numlen > 0;
12151         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12152             fprintf(f, "\n");
12153             linelen = 0;
12154             blank = 0;
12155         }
12156         if (blank) {
12157             fprintf(f, " ");
12158             linelen++;
12159         }
12160         fprintf(f, "%s", numtext);
12161         linelen += numlen;
12162
12163         /* Get move */
12164         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12165         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12166
12167         /* Print move */
12168         blank = linelen > 0 && movelen > 0;
12169         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12170             fprintf(f, "\n");
12171             linelen = 0;
12172             blank = 0;
12173         }
12174         if (blank) {
12175             fprintf(f, " ");
12176             linelen++;
12177         }
12178         fprintf(f, "%s", move_buffer);
12179         linelen += movelen;
12180
12181         /* [AS] Add PV info if present */
12182         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12183             /* [HGM] add time */
12184             char buf[MSG_SIZ]; int seconds;
12185
12186             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12187
12188             if( seconds <= 0)
12189               buf[0] = 0;
12190             else
12191               if( seconds < 30 )
12192                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12193               else
12194                 {
12195                   seconds = (seconds + 4)/10; // round to full seconds
12196                   if( seconds < 60 )
12197                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12198                   else
12199                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12200                 }
12201
12202             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12203                       pvInfoList[i].score >= 0 ? "+" : "",
12204                       pvInfoList[i].score / 100.0,
12205                       pvInfoList[i].depth,
12206                       buf );
12207
12208             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12209
12210             /* Print score/depth */
12211             blank = linelen > 0 && movelen > 0;
12212             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12213                 fprintf(f, "\n");
12214                 linelen = 0;
12215                 blank = 0;
12216             }
12217             if (blank) {
12218                 fprintf(f, " ");
12219                 linelen++;
12220             }
12221             fprintf(f, "%s", move_buffer);
12222             linelen += movelen;
12223         }
12224
12225         i++;
12226     }
12227
12228     /* Start a new line */
12229     if (linelen > 0) fprintf(f, "\n");
12230
12231     /* Print comments after last move */
12232     if (commentList[i] != NULL) {
12233         fprintf(f, "%s\n", commentList[i]);
12234     }
12235
12236     /* Print result */
12237     if (gameInfo.resultDetails != NULL &&
12238         gameInfo.resultDetails[0] != NULLCHAR) {
12239         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12240                 PGNResult(gameInfo.result));
12241     } else {
12242         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12243     }
12244
12245     fclose(f);
12246     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12247     return TRUE;
12248 }
12249
12250 /* Save game in old style and close the file */
12251 int
12252 SaveGameOldStyle(f)
12253      FILE *f;
12254 {
12255     int i, offset;
12256     time_t tm;
12257
12258     tm = time((time_t *) NULL);
12259
12260     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12261     PrintOpponents(f);
12262
12263     if (backwardMostMove > 0 || startedFromSetupPosition) {
12264         fprintf(f, "\n[--------------\n");
12265         PrintPosition(f, backwardMostMove);
12266         fprintf(f, "--------------]\n");
12267     } else {
12268         fprintf(f, "\n");
12269     }
12270
12271     i = backwardMostMove;
12272     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12273
12274     while (i < forwardMostMove) {
12275         if (commentList[i] != NULL) {
12276             fprintf(f, "[%s]\n", commentList[i]);
12277         }
12278
12279         if ((i % 2) == 1) {
12280             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12281             i++;
12282         } else {
12283             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12284             i++;
12285             if (commentList[i] != NULL) {
12286                 fprintf(f, "\n");
12287                 continue;
12288             }
12289             if (i >= forwardMostMove) {
12290                 fprintf(f, "\n");
12291                 break;
12292             }
12293             fprintf(f, "%s\n", parseList[i]);
12294             i++;
12295         }
12296     }
12297
12298     if (commentList[i] != NULL) {
12299         fprintf(f, "[%s]\n", commentList[i]);
12300     }
12301
12302     /* This isn't really the old style, but it's close enough */
12303     if (gameInfo.resultDetails != NULL &&
12304         gameInfo.resultDetails[0] != NULLCHAR) {
12305         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12306                 gameInfo.resultDetails);
12307     } else {
12308         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12309     }
12310
12311     fclose(f);
12312     return TRUE;
12313 }
12314
12315 /* Save the current game to open file f and close the file */
12316 int
12317 SaveGame(f, dummy, dummy2)
12318      FILE *f;
12319      int dummy;
12320      char *dummy2;
12321 {
12322     if (gameMode == EditPosition) EditPositionDone(TRUE);
12323     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12324     if (appData.oldSaveStyle)
12325       return SaveGameOldStyle(f);
12326     else
12327       return SaveGamePGN(f);
12328 }
12329
12330 /* Save the current position to the given file */
12331 int
12332 SavePositionToFile(filename)
12333      char *filename;
12334 {
12335     FILE *f;
12336     char buf[MSG_SIZ];
12337
12338     if (strcmp(filename, "-") == 0) {
12339         return SavePosition(stdout, 0, NULL);
12340     } else {
12341         f = fopen(filename, "a");
12342         if (f == NULL) {
12343             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12344             DisplayError(buf, errno);
12345             return FALSE;
12346         } else {
12347             safeStrCpy(buf, lastMsg, MSG_SIZ);
12348             DisplayMessage(_("Waiting for access to save file"), "");
12349             flock(fileno(f), LOCK_EX); // [HGM] lock
12350             DisplayMessage(_("Saving position"), "");
12351             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12352             SavePosition(f, 0, NULL);
12353             DisplayMessage(buf, "");
12354             return TRUE;
12355         }
12356     }
12357 }
12358
12359 /* Save the current position to the given open file and close the file */
12360 int
12361 SavePosition(f, dummy, dummy2)
12362      FILE *f;
12363      int dummy;
12364      char *dummy2;
12365 {
12366     time_t tm;
12367     char *fen;
12368
12369     if (gameMode == EditPosition) EditPositionDone(TRUE);
12370     if (appData.oldSaveStyle) {
12371         tm = time((time_t *) NULL);
12372
12373         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12374         PrintOpponents(f);
12375         fprintf(f, "[--------------\n");
12376         PrintPosition(f, currentMove);
12377         fprintf(f, "--------------]\n");
12378     } else {
12379         fen = PositionToFEN(currentMove, NULL);
12380         fprintf(f, "%s\n", fen);
12381         free(fen);
12382     }
12383     fclose(f);
12384     return TRUE;
12385 }
12386
12387 void
12388 ReloadCmailMsgEvent(unregister)
12389      int unregister;
12390 {
12391 #if !WIN32
12392     static char *inFilename = NULL;
12393     static char *outFilename;
12394     int i;
12395     struct stat inbuf, outbuf;
12396     int status;
12397
12398     /* Any registered moves are unregistered if unregister is set, */
12399     /* i.e. invoked by the signal handler */
12400     if (unregister) {
12401         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12402             cmailMoveRegistered[i] = FALSE;
12403             if (cmailCommentList[i] != NULL) {
12404                 free(cmailCommentList[i]);
12405                 cmailCommentList[i] = NULL;
12406             }
12407         }
12408         nCmailMovesRegistered = 0;
12409     }
12410
12411     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12412         cmailResult[i] = CMAIL_NOT_RESULT;
12413     }
12414     nCmailResults = 0;
12415
12416     if (inFilename == NULL) {
12417         /* Because the filenames are static they only get malloced once  */
12418         /* and they never get freed                                      */
12419         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12420         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12421
12422         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12423         sprintf(outFilename, "%s.out", appData.cmailGameName);
12424     }
12425
12426     status = stat(outFilename, &outbuf);
12427     if (status < 0) {
12428         cmailMailedMove = FALSE;
12429     } else {
12430         status = stat(inFilename, &inbuf);
12431         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12432     }
12433
12434     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12435        counts the games, notes how each one terminated, etc.
12436
12437        It would be nice to remove this kludge and instead gather all
12438        the information while building the game list.  (And to keep it
12439        in the game list nodes instead of having a bunch of fixed-size
12440        parallel arrays.)  Note this will require getting each game's
12441        termination from the PGN tags, as the game list builder does
12442        not process the game moves.  --mann
12443        */
12444     cmailMsgLoaded = TRUE;
12445     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12446
12447     /* Load first game in the file or popup game menu */
12448     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12449
12450 #endif /* !WIN32 */
12451     return;
12452 }
12453
12454 int
12455 RegisterMove()
12456 {
12457     FILE *f;
12458     char string[MSG_SIZ];
12459
12460     if (   cmailMailedMove
12461         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12462         return TRUE;            /* Allow free viewing  */
12463     }
12464
12465     /* Unregister move to ensure that we don't leave RegisterMove        */
12466     /* with the move registered when the conditions for registering no   */
12467     /* longer hold                                                       */
12468     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12469         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12470         nCmailMovesRegistered --;
12471
12472         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12473           {
12474               free(cmailCommentList[lastLoadGameNumber - 1]);
12475               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12476           }
12477     }
12478
12479     if (cmailOldMove == -1) {
12480         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12481         return FALSE;
12482     }
12483
12484     if (currentMove > cmailOldMove + 1) {
12485         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12486         return FALSE;
12487     }
12488
12489     if (currentMove < cmailOldMove) {
12490         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12491         return FALSE;
12492     }
12493
12494     if (forwardMostMove > currentMove) {
12495         /* Silently truncate extra moves */
12496         TruncateGame();
12497     }
12498
12499     if (   (currentMove == cmailOldMove + 1)
12500         || (   (currentMove == cmailOldMove)
12501             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12502                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12503         if (gameInfo.result != GameUnfinished) {
12504             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12505         }
12506
12507         if (commentList[currentMove] != NULL) {
12508             cmailCommentList[lastLoadGameNumber - 1]
12509               = StrSave(commentList[currentMove]);
12510         }
12511         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12512
12513         if (appData.debugMode)
12514           fprintf(debugFP, "Saving %s for game %d\n",
12515                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12516
12517         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12518
12519         f = fopen(string, "w");
12520         if (appData.oldSaveStyle) {
12521             SaveGameOldStyle(f); /* also closes the file */
12522
12523             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12524             f = fopen(string, "w");
12525             SavePosition(f, 0, NULL); /* also closes the file */
12526         } else {
12527             fprintf(f, "{--------------\n");
12528             PrintPosition(f, currentMove);
12529             fprintf(f, "--------------}\n\n");
12530
12531             SaveGame(f, 0, NULL); /* also closes the file*/
12532         }
12533
12534         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12535         nCmailMovesRegistered ++;
12536     } else if (nCmailGames == 1) {
12537         DisplayError(_("You have not made a move yet"), 0);
12538         return FALSE;
12539     }
12540
12541     return TRUE;
12542 }
12543
12544 void
12545 MailMoveEvent()
12546 {
12547 #if !WIN32
12548     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12549     FILE *commandOutput;
12550     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12551     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12552     int nBuffers;
12553     int i;
12554     int archived;
12555     char *arcDir;
12556
12557     if (! cmailMsgLoaded) {
12558         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12559         return;
12560     }
12561
12562     if (nCmailGames == nCmailResults) {
12563         DisplayError(_("No unfinished games"), 0);
12564         return;
12565     }
12566
12567 #if CMAIL_PROHIBIT_REMAIL
12568     if (cmailMailedMove) {
12569       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);
12570         DisplayError(msg, 0);
12571         return;
12572     }
12573 #endif
12574
12575     if (! (cmailMailedMove || RegisterMove())) return;
12576
12577     if (   cmailMailedMove
12578         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12579       snprintf(string, MSG_SIZ, partCommandString,
12580                appData.debugMode ? " -v" : "", appData.cmailGameName);
12581         commandOutput = popen(string, "r");
12582
12583         if (commandOutput == NULL) {
12584             DisplayError(_("Failed to invoke cmail"), 0);
12585         } else {
12586             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12587                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12588             }
12589             if (nBuffers > 1) {
12590                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12591                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12592                 nBytes = MSG_SIZ - 1;
12593             } else {
12594                 (void) memcpy(msg, buffer, nBytes);
12595             }
12596             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12597
12598             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12599                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12600
12601                 archived = TRUE;
12602                 for (i = 0; i < nCmailGames; i ++) {
12603                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12604                         archived = FALSE;
12605                     }
12606                 }
12607                 if (   archived
12608                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12609                         != NULL)) {
12610                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12611                            arcDir,
12612                            appData.cmailGameName,
12613                            gameInfo.date);
12614                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12615                     cmailMsgLoaded = FALSE;
12616                 }
12617             }
12618
12619             DisplayInformation(msg);
12620             pclose(commandOutput);
12621         }
12622     } else {
12623         if ((*cmailMsg) != '\0') {
12624             DisplayInformation(cmailMsg);
12625         }
12626     }
12627
12628     return;
12629 #endif /* !WIN32 */
12630 }
12631
12632 char *
12633 CmailMsg()
12634 {
12635 #if WIN32
12636     return NULL;
12637 #else
12638     int  prependComma = 0;
12639     char number[5];
12640     char string[MSG_SIZ];       /* Space for game-list */
12641     int  i;
12642
12643     if (!cmailMsgLoaded) return "";
12644
12645     if (cmailMailedMove) {
12646       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12647     } else {
12648         /* Create a list of games left */
12649       snprintf(string, MSG_SIZ, "[");
12650         for (i = 0; i < nCmailGames; i ++) {
12651             if (! (   cmailMoveRegistered[i]
12652                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12653                 if (prependComma) {
12654                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12655                 } else {
12656                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12657                     prependComma = 1;
12658                 }
12659
12660                 strcat(string, number);
12661             }
12662         }
12663         strcat(string, "]");
12664
12665         if (nCmailMovesRegistered + nCmailResults == 0) {
12666             switch (nCmailGames) {
12667               case 1:
12668                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12669                 break;
12670
12671               case 2:
12672                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12673                 break;
12674
12675               default:
12676                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12677                          nCmailGames);
12678                 break;
12679             }
12680         } else {
12681             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12682               case 1:
12683                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12684                          string);
12685                 break;
12686
12687               case 0:
12688                 if (nCmailResults == nCmailGames) {
12689                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12690                 } else {
12691                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12692                 }
12693                 break;
12694
12695               default:
12696                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12697                          string);
12698             }
12699         }
12700     }
12701     return cmailMsg;
12702 #endif /* WIN32 */
12703 }
12704
12705 void
12706 ResetGameEvent()
12707 {
12708     if (gameMode == Training)
12709       SetTrainingModeOff();
12710
12711     Reset(TRUE, TRUE);
12712     cmailMsgLoaded = FALSE;
12713     if (appData.icsActive) {
12714       SendToICS(ics_prefix);
12715       SendToICS("refresh\n");
12716     }
12717 }
12718
12719 void
12720 ExitEvent(status)
12721      int status;
12722 {
12723     exiting++;
12724     if (exiting > 2) {
12725       /* Give up on clean exit */
12726       exit(status);
12727     }
12728     if (exiting > 1) {
12729       /* Keep trying for clean exit */
12730       return;
12731     }
12732
12733     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12734
12735     if (telnetISR != NULL) {
12736       RemoveInputSource(telnetISR);
12737     }
12738     if (icsPR != NoProc) {
12739       DestroyChildProcess(icsPR, TRUE);
12740     }
12741
12742     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12743     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12744
12745     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12746     /* make sure this other one finishes before killing it!                  */
12747     if(endingGame) { int count = 0;
12748         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12749         while(endingGame && count++ < 10) DoSleep(1);
12750         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12751     }
12752
12753     /* Kill off chess programs */
12754     if (first.pr != NoProc) {
12755         ExitAnalyzeMode();
12756
12757         DoSleep( appData.delayBeforeQuit );
12758         SendToProgram("quit\n", &first);
12759         DoSleep( appData.delayAfterQuit );
12760         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12761     }
12762     if (second.pr != NoProc) {
12763         DoSleep( appData.delayBeforeQuit );
12764         SendToProgram("quit\n", &second);
12765         DoSleep( appData.delayAfterQuit );
12766         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12767     }
12768     if (first.isr != NULL) {
12769         RemoveInputSource(first.isr);
12770     }
12771     if (second.isr != NULL) {
12772         RemoveInputSource(second.isr);
12773     }
12774
12775     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12776     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12777
12778     ShutDownFrontEnd();
12779     exit(status);
12780 }
12781
12782 void
12783 PauseEvent()
12784 {
12785     if (appData.debugMode)
12786         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12787     if (pausing) {
12788         pausing = FALSE;
12789         ModeHighlight();
12790         if (gameMode == MachinePlaysWhite ||
12791             gameMode == MachinePlaysBlack) {
12792             StartClocks();
12793         } else {
12794             DisplayBothClocks();
12795         }
12796         if (gameMode == PlayFromGameFile) {
12797             if (appData.timeDelay >= 0)
12798                 AutoPlayGameLoop();
12799         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12800             Reset(FALSE, TRUE);
12801             SendToICS(ics_prefix);
12802             SendToICS("refresh\n");
12803         } else if (currentMove < forwardMostMove) {
12804             ForwardInner(forwardMostMove);
12805         }
12806         pauseExamInvalid = FALSE;
12807     } else {
12808         switch (gameMode) {
12809           default:
12810             return;
12811           case IcsExamining:
12812             pauseExamForwardMostMove = forwardMostMove;
12813             pauseExamInvalid = FALSE;
12814             /* fall through */
12815           case IcsObserving:
12816           case IcsPlayingWhite:
12817           case IcsPlayingBlack:
12818             pausing = TRUE;
12819             ModeHighlight();
12820             return;
12821           case PlayFromGameFile:
12822             (void) StopLoadGameTimer();
12823             pausing = TRUE;
12824             ModeHighlight();
12825             break;
12826           case BeginningOfGame:
12827             if (appData.icsActive) return;
12828             /* else fall through */
12829           case MachinePlaysWhite:
12830           case MachinePlaysBlack:
12831           case TwoMachinesPlay:
12832             if (forwardMostMove == 0)
12833               return;           /* don't pause if no one has moved */
12834             if ((gameMode == MachinePlaysWhite &&
12835                  !WhiteOnMove(forwardMostMove)) ||
12836                 (gameMode == MachinePlaysBlack &&
12837                  WhiteOnMove(forwardMostMove))) {
12838                 StopClocks();
12839             }
12840             pausing = TRUE;
12841             ModeHighlight();
12842             break;
12843         }
12844     }
12845 }
12846
12847 void
12848 EditCommentEvent()
12849 {
12850     char title[MSG_SIZ];
12851
12852     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12853       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12854     } else {
12855       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12856                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12857                parseList[currentMove - 1]);
12858     }
12859
12860     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12861 }
12862
12863
12864 void
12865 EditTagsEvent()
12866 {
12867     char *tags = PGNTags(&gameInfo);
12868     bookUp = FALSE;
12869     EditTagsPopUp(tags, NULL);
12870     free(tags);
12871 }
12872
12873 void
12874 AnalyzeModeEvent()
12875 {
12876     if (appData.noChessProgram || gameMode == AnalyzeMode)
12877       return;
12878
12879     if (gameMode != AnalyzeFile) {
12880         if (!appData.icsEngineAnalyze) {
12881                EditGameEvent();
12882                if (gameMode != EditGame) return;
12883         }
12884         ResurrectChessProgram();
12885         SendToProgram("analyze\n", &first);
12886         first.analyzing = TRUE;
12887         /*first.maybeThinking = TRUE;*/
12888         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12889         EngineOutputPopUp();
12890     }
12891     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12892     pausing = FALSE;
12893     ModeHighlight();
12894     SetGameInfo();
12895
12896     StartAnalysisClock();
12897     GetTimeMark(&lastNodeCountTime);
12898     lastNodeCount = 0;
12899 }
12900
12901 void
12902 AnalyzeFileEvent()
12903 {
12904     if (appData.noChessProgram || gameMode == AnalyzeFile)
12905       return;
12906
12907     if (gameMode != AnalyzeMode) {
12908         EditGameEvent();
12909         if (gameMode != EditGame) return;
12910         ResurrectChessProgram();
12911         SendToProgram("analyze\n", &first);
12912         first.analyzing = TRUE;
12913         /*first.maybeThinking = TRUE;*/
12914         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12915         EngineOutputPopUp();
12916     }
12917     gameMode = AnalyzeFile;
12918     pausing = FALSE;
12919     ModeHighlight();
12920     SetGameInfo();
12921
12922     StartAnalysisClock();
12923     GetTimeMark(&lastNodeCountTime);
12924     lastNodeCount = 0;
12925     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12926 }
12927
12928 void
12929 MachineWhiteEvent()
12930 {
12931     char buf[MSG_SIZ];
12932     char *bookHit = NULL;
12933
12934     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12935       return;
12936
12937
12938     if (gameMode == PlayFromGameFile ||
12939         gameMode == TwoMachinesPlay  ||
12940         gameMode == Training         ||
12941         gameMode == AnalyzeMode      ||
12942         gameMode == EndOfGame)
12943         EditGameEvent();
12944
12945     if (gameMode == EditPosition)
12946         EditPositionDone(TRUE);
12947
12948     if (!WhiteOnMove(currentMove)) {
12949         DisplayError(_("It is not White's turn"), 0);
12950         return;
12951     }
12952
12953     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12954       ExitAnalyzeMode();
12955
12956     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12957         gameMode == AnalyzeFile)
12958         TruncateGame();
12959
12960     ResurrectChessProgram();    /* in case it isn't running */
12961     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12962         gameMode = MachinePlaysWhite;
12963         ResetClocks();
12964     } else
12965     gameMode = MachinePlaysWhite;
12966     pausing = FALSE;
12967     ModeHighlight();
12968     SetGameInfo();
12969     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12970     DisplayTitle(buf);
12971     if (first.sendName) {
12972       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12973       SendToProgram(buf, &first);
12974     }
12975     if (first.sendTime) {
12976       if (first.useColors) {
12977         SendToProgram("black\n", &first); /*gnu kludge*/
12978       }
12979       SendTimeRemaining(&first, TRUE);
12980     }
12981     if (first.useColors) {
12982       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12983     }
12984     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12985     SetMachineThinkingEnables();
12986     first.maybeThinking = TRUE;
12987     StartClocks();
12988     firstMove = FALSE;
12989
12990     if (appData.autoFlipView && !flipView) {
12991       flipView = !flipView;
12992       DrawPosition(FALSE, NULL);
12993       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12994     }
12995
12996     if(bookHit) { // [HGM] book: simulate book reply
12997         static char bookMove[MSG_SIZ]; // a bit generous?
12998
12999         programStats.nodes = programStats.depth = programStats.time =
13000         programStats.score = programStats.got_only_move = 0;
13001         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13002
13003         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13004         strcat(bookMove, bookHit);
13005         HandleMachineMove(bookMove, &first);
13006     }
13007 }
13008
13009 void
13010 MachineBlackEvent()
13011 {
13012   char buf[MSG_SIZ];
13013   char *bookHit = NULL;
13014
13015     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13016         return;
13017
13018
13019     if (gameMode == PlayFromGameFile ||
13020         gameMode == TwoMachinesPlay  ||
13021         gameMode == Training         ||
13022         gameMode == AnalyzeMode      ||
13023         gameMode == EndOfGame)
13024         EditGameEvent();
13025
13026     if (gameMode == EditPosition)
13027         EditPositionDone(TRUE);
13028
13029     if (WhiteOnMove(currentMove)) {
13030         DisplayError(_("It is not Black's turn"), 0);
13031         return;
13032     }
13033
13034     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13035       ExitAnalyzeMode();
13036
13037     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13038         gameMode == AnalyzeFile)
13039         TruncateGame();
13040
13041     ResurrectChessProgram();    /* in case it isn't running */
13042     gameMode = MachinePlaysBlack;
13043     pausing = FALSE;
13044     ModeHighlight();
13045     SetGameInfo();
13046     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13047     DisplayTitle(buf);
13048     if (first.sendName) {
13049       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13050       SendToProgram(buf, &first);
13051     }
13052     if (first.sendTime) {
13053       if (first.useColors) {
13054         SendToProgram("white\n", &first); /*gnu kludge*/
13055       }
13056       SendTimeRemaining(&first, FALSE);
13057     }
13058     if (first.useColors) {
13059       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13060     }
13061     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13062     SetMachineThinkingEnables();
13063     first.maybeThinking = TRUE;
13064     StartClocks();
13065
13066     if (appData.autoFlipView && flipView) {
13067       flipView = !flipView;
13068       DrawPosition(FALSE, NULL);
13069       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13070     }
13071     if(bookHit) { // [HGM] book: simulate book reply
13072         static char bookMove[MSG_SIZ]; // a bit generous?
13073
13074         programStats.nodes = programStats.depth = programStats.time =
13075         programStats.score = programStats.got_only_move = 0;
13076         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13077
13078         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13079         strcat(bookMove, bookHit);
13080         HandleMachineMove(bookMove, &first);
13081     }
13082 }
13083
13084
13085 void
13086 DisplayTwoMachinesTitle()
13087 {
13088     char buf[MSG_SIZ];
13089     if (appData.matchGames > 0) {
13090         if(appData.tourneyFile[0]) {
13091           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13092                    gameInfo.white, gameInfo.black,
13093                    nextGame+1, appData.matchGames+1,
13094                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13095         } else 
13096         if (first.twoMachinesColor[0] == 'w') {
13097           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13098                    gameInfo.white, gameInfo.black,
13099                    first.matchWins, second.matchWins,
13100                    matchGame - 1 - (first.matchWins + second.matchWins));
13101         } else {
13102           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13103                    gameInfo.white, gameInfo.black,
13104                    second.matchWins, first.matchWins,
13105                    matchGame - 1 - (first.matchWins + second.matchWins));
13106         }
13107     } else {
13108       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13109     }
13110     DisplayTitle(buf);
13111 }
13112
13113 void
13114 SettingsMenuIfReady()
13115 {
13116   if (second.lastPing != second.lastPong) {
13117     DisplayMessage("", _("Waiting for second chess program"));
13118     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13119     return;
13120   }
13121   ThawUI();
13122   DisplayMessage("", "");
13123   SettingsPopUp(&second);
13124 }
13125
13126 int
13127 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13128 {
13129     char buf[MSG_SIZ];
13130     if (cps->pr == NULL) {
13131         StartChessProgram(cps);
13132         if (cps->protocolVersion == 1) {
13133           retry();
13134         } else {
13135           /* kludge: allow timeout for initial "feature" command */
13136           FreezeUI();
13137           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13138           DisplayMessage("", buf);
13139           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13140         }
13141         return 1;
13142     }
13143     return 0;
13144 }
13145
13146 void
13147 TwoMachinesEvent P((void))
13148 {
13149     int i;
13150     char buf[MSG_SIZ];
13151     ChessProgramState *onmove;
13152     char *bookHit = NULL;
13153     static int stalling = 0;
13154     TimeMark now;
13155     long wait;
13156
13157     if (appData.noChessProgram) return;
13158
13159     switch (gameMode) {
13160       case TwoMachinesPlay:
13161         return;
13162       case MachinePlaysWhite:
13163       case MachinePlaysBlack:
13164         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13165             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13166             return;
13167         }
13168         /* fall through */
13169       case BeginningOfGame:
13170       case PlayFromGameFile:
13171       case EndOfGame:
13172         EditGameEvent();
13173         if (gameMode != EditGame) return;
13174         break;
13175       case EditPosition:
13176         EditPositionDone(TRUE);
13177         break;
13178       case AnalyzeMode:
13179       case AnalyzeFile:
13180         ExitAnalyzeMode();
13181         break;
13182       case EditGame:
13183       default:
13184         break;
13185     }
13186
13187 //    forwardMostMove = currentMove;
13188     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13189
13190     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13191
13192     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13193     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13194       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13195       return;
13196     }
13197     if(!stalling) {
13198       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13199       SendToProgram("force\n", &second);
13200       stalling = 1;
13201       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13202       return;
13203     }
13204     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13205     if(appData.matchPause>10000 || appData.matchPause<10)
13206                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13207     wait = SubtractTimeMarks(&now, &pauseStart);
13208     if(wait < appData.matchPause) {
13209         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13210         return;
13211     }
13212     stalling = 0;
13213     DisplayMessage("", "");
13214     if (startedFromSetupPosition) {
13215         SendBoard(&second, backwardMostMove);
13216     if (appData.debugMode) {
13217         fprintf(debugFP, "Two Machines\n");
13218     }
13219     }
13220     for (i = backwardMostMove; i < forwardMostMove; i++) {
13221         SendMoveToProgram(i, &second);
13222     }
13223
13224     gameMode = TwoMachinesPlay;
13225     pausing = FALSE;
13226     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13227     SetGameInfo();
13228     DisplayTwoMachinesTitle();
13229     firstMove = TRUE;
13230     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13231         onmove = &first;
13232     } else {
13233         onmove = &second;
13234     }
13235     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13236     SendToProgram(first.computerString, &first);
13237     if (first.sendName) {
13238       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13239       SendToProgram(buf, &first);
13240     }
13241     SendToProgram(second.computerString, &second);
13242     if (second.sendName) {
13243       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13244       SendToProgram(buf, &second);
13245     }
13246
13247     ResetClocks();
13248     if (!first.sendTime || !second.sendTime) {
13249         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13250         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13251     }
13252     if (onmove->sendTime) {
13253       if (onmove->useColors) {
13254         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13255       }
13256       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13257     }
13258     if (onmove->useColors) {
13259       SendToProgram(onmove->twoMachinesColor, onmove);
13260     }
13261     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13262 //    SendToProgram("go\n", onmove);
13263     onmove->maybeThinking = TRUE;
13264     SetMachineThinkingEnables();
13265
13266     StartClocks();
13267
13268     if(bookHit) { // [HGM] book: simulate book reply
13269         static char bookMove[MSG_SIZ]; // a bit generous?
13270
13271         programStats.nodes = programStats.depth = programStats.time =
13272         programStats.score = programStats.got_only_move = 0;
13273         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13274
13275         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13276         strcat(bookMove, bookHit);
13277         savedMessage = bookMove; // args for deferred call
13278         savedState = onmove;
13279         ScheduleDelayedEvent(DeferredBookMove, 1);
13280     }
13281 }
13282
13283 void
13284 TrainingEvent()
13285 {
13286     if (gameMode == Training) {
13287       SetTrainingModeOff();
13288       gameMode = PlayFromGameFile;
13289       DisplayMessage("", _("Training mode off"));
13290     } else {
13291       gameMode = Training;
13292       animateTraining = appData.animate;
13293
13294       /* make sure we are not already at the end of the game */
13295       if (currentMove < forwardMostMove) {
13296         SetTrainingModeOn();
13297         DisplayMessage("", _("Training mode on"));
13298       } else {
13299         gameMode = PlayFromGameFile;
13300         DisplayError(_("Already at end of game"), 0);
13301       }
13302     }
13303     ModeHighlight();
13304 }
13305
13306 void
13307 IcsClientEvent()
13308 {
13309     if (!appData.icsActive) return;
13310     switch (gameMode) {
13311       case IcsPlayingWhite:
13312       case IcsPlayingBlack:
13313       case IcsObserving:
13314       case IcsIdle:
13315       case BeginningOfGame:
13316       case IcsExamining:
13317         return;
13318
13319       case EditGame:
13320         break;
13321
13322       case EditPosition:
13323         EditPositionDone(TRUE);
13324         break;
13325
13326       case AnalyzeMode:
13327       case AnalyzeFile:
13328         ExitAnalyzeMode();
13329         break;
13330
13331       default:
13332         EditGameEvent();
13333         break;
13334     }
13335
13336     gameMode = IcsIdle;
13337     ModeHighlight();
13338     return;
13339 }
13340
13341
13342 void
13343 EditGameEvent()
13344 {
13345     int i;
13346
13347     switch (gameMode) {
13348       case Training:
13349         SetTrainingModeOff();
13350         break;
13351       case MachinePlaysWhite:
13352       case MachinePlaysBlack:
13353       case BeginningOfGame:
13354         SendToProgram("force\n", &first);
13355         SetUserThinkingEnables();
13356         break;
13357       case PlayFromGameFile:
13358         (void) StopLoadGameTimer();
13359         if (gameFileFP != NULL) {
13360             gameFileFP = NULL;
13361         }
13362         break;
13363       case EditPosition:
13364         EditPositionDone(TRUE);
13365         break;
13366       case AnalyzeMode:
13367       case AnalyzeFile:
13368         ExitAnalyzeMode();
13369         SendToProgram("force\n", &first);
13370         break;
13371       case TwoMachinesPlay:
13372         GameEnds(EndOfFile, NULL, GE_PLAYER);
13373         ResurrectChessProgram();
13374         SetUserThinkingEnables();
13375         break;
13376       case EndOfGame:
13377         ResurrectChessProgram();
13378         break;
13379       case IcsPlayingBlack:
13380       case IcsPlayingWhite:
13381         DisplayError(_("Warning: You are still playing a game"), 0);
13382         break;
13383       case IcsObserving:
13384         DisplayError(_("Warning: You are still observing a game"), 0);
13385         break;
13386       case IcsExamining:
13387         DisplayError(_("Warning: You are still examining a game"), 0);
13388         break;
13389       case IcsIdle:
13390         break;
13391       case EditGame:
13392       default:
13393         return;
13394     }
13395
13396     pausing = FALSE;
13397     StopClocks();
13398     first.offeredDraw = second.offeredDraw = 0;
13399
13400     if (gameMode == PlayFromGameFile) {
13401         whiteTimeRemaining = timeRemaining[0][currentMove];
13402         blackTimeRemaining = timeRemaining[1][currentMove];
13403         DisplayTitle("");
13404     }
13405
13406     if (gameMode == MachinePlaysWhite ||
13407         gameMode == MachinePlaysBlack ||
13408         gameMode == TwoMachinesPlay ||
13409         gameMode == EndOfGame) {
13410         i = forwardMostMove;
13411         while (i > currentMove) {
13412             SendToProgram("undo\n", &first);
13413             i--;
13414         }
13415         whiteTimeRemaining = timeRemaining[0][currentMove];
13416         blackTimeRemaining = timeRemaining[1][currentMove];
13417         DisplayBothClocks();
13418         if (whiteFlag || blackFlag) {
13419             whiteFlag = blackFlag = 0;
13420         }
13421         DisplayTitle("");
13422     }
13423
13424     gameMode = EditGame;
13425     ModeHighlight();
13426     SetGameInfo();
13427 }
13428
13429
13430 void
13431 EditPositionEvent()
13432 {
13433     if (gameMode == EditPosition) {
13434         EditGameEvent();
13435         return;
13436     }
13437
13438     EditGameEvent();
13439     if (gameMode != EditGame) return;
13440
13441     gameMode = EditPosition;
13442     ModeHighlight();
13443     SetGameInfo();
13444     if (currentMove > 0)
13445       CopyBoard(boards[0], boards[currentMove]);
13446
13447     blackPlaysFirst = !WhiteOnMove(currentMove);
13448     ResetClocks();
13449     currentMove = forwardMostMove = backwardMostMove = 0;
13450     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13451     DisplayMove(-1);
13452 }
13453
13454 void
13455 ExitAnalyzeMode()
13456 {
13457     /* [DM] icsEngineAnalyze - possible call from other functions */
13458     if (appData.icsEngineAnalyze) {
13459         appData.icsEngineAnalyze = FALSE;
13460
13461         DisplayMessage("",_("Close ICS engine analyze..."));
13462     }
13463     if (first.analysisSupport && first.analyzing) {
13464       SendToProgram("exit\n", &first);
13465       first.analyzing = FALSE;
13466     }
13467     thinkOutput[0] = NULLCHAR;
13468 }
13469
13470 void
13471 EditPositionDone(Boolean fakeRights)
13472 {
13473     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13474
13475     startedFromSetupPosition = TRUE;
13476     InitChessProgram(&first, FALSE);
13477     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13478       boards[0][EP_STATUS] = EP_NONE;
13479       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13480     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13481         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13482         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13483       } else boards[0][CASTLING][2] = NoRights;
13484     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13485         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13486         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13487       } else boards[0][CASTLING][5] = NoRights;
13488     }
13489     SendToProgram("force\n", &first);
13490     if (blackPlaysFirst) {
13491         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13492         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13493         currentMove = forwardMostMove = backwardMostMove = 1;
13494         CopyBoard(boards[1], boards[0]);
13495     } else {
13496         currentMove = forwardMostMove = backwardMostMove = 0;
13497     }
13498     SendBoard(&first, forwardMostMove);
13499     if (appData.debugMode) {
13500         fprintf(debugFP, "EditPosDone\n");
13501     }
13502     DisplayTitle("");
13503     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13504     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13505     gameMode = EditGame;
13506     ModeHighlight();
13507     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13508     ClearHighlights(); /* [AS] */
13509 }
13510
13511 /* Pause for `ms' milliseconds */
13512 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13513 void
13514 TimeDelay(ms)
13515      long ms;
13516 {
13517     TimeMark m1, m2;
13518
13519     GetTimeMark(&m1);
13520     do {
13521         GetTimeMark(&m2);
13522     } while (SubtractTimeMarks(&m2, &m1) < ms);
13523 }
13524
13525 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13526 void
13527 SendMultiLineToICS(buf)
13528      char *buf;
13529 {
13530     char temp[MSG_SIZ+1], *p;
13531     int len;
13532
13533     len = strlen(buf);
13534     if (len > MSG_SIZ)
13535       len = MSG_SIZ;
13536
13537     strncpy(temp, buf, len);
13538     temp[len] = 0;
13539
13540     p = temp;
13541     while (*p) {
13542         if (*p == '\n' || *p == '\r')
13543           *p = ' ';
13544         ++p;
13545     }
13546
13547     strcat(temp, "\n");
13548     SendToICS(temp);
13549     SendToPlayer(temp, strlen(temp));
13550 }
13551
13552 void
13553 SetWhiteToPlayEvent()
13554 {
13555     if (gameMode == EditPosition) {
13556         blackPlaysFirst = FALSE;
13557         DisplayBothClocks();    /* works because currentMove is 0 */
13558     } else if (gameMode == IcsExamining) {
13559         SendToICS(ics_prefix);
13560         SendToICS("tomove white\n");
13561     }
13562 }
13563
13564 void
13565 SetBlackToPlayEvent()
13566 {
13567     if (gameMode == EditPosition) {
13568         blackPlaysFirst = TRUE;
13569         currentMove = 1;        /* kludge */
13570         DisplayBothClocks();
13571         currentMove = 0;
13572     } else if (gameMode == IcsExamining) {
13573         SendToICS(ics_prefix);
13574         SendToICS("tomove black\n");
13575     }
13576 }
13577
13578 void
13579 EditPositionMenuEvent(selection, x, y)
13580      ChessSquare selection;
13581      int x, y;
13582 {
13583     char buf[MSG_SIZ];
13584     ChessSquare piece = boards[0][y][x];
13585
13586     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13587
13588     switch (selection) {
13589       case ClearBoard:
13590         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13591             SendToICS(ics_prefix);
13592             SendToICS("bsetup clear\n");
13593         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13594             SendToICS(ics_prefix);
13595             SendToICS("clearboard\n");
13596         } else {
13597             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13598                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13599                 for (y = 0; y < BOARD_HEIGHT; y++) {
13600                     if (gameMode == IcsExamining) {
13601                         if (boards[currentMove][y][x] != EmptySquare) {
13602                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13603                                     AAA + x, ONE + y);
13604                             SendToICS(buf);
13605                         }
13606                     } else {
13607                         boards[0][y][x] = p;
13608                     }
13609                 }
13610             }
13611         }
13612         if (gameMode == EditPosition) {
13613             DrawPosition(FALSE, boards[0]);
13614         }
13615         break;
13616
13617       case WhitePlay:
13618         SetWhiteToPlayEvent();
13619         break;
13620
13621       case BlackPlay:
13622         SetBlackToPlayEvent();
13623         break;
13624
13625       case EmptySquare:
13626         if (gameMode == IcsExamining) {
13627             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13628             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13629             SendToICS(buf);
13630         } else {
13631             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13632                 if(x == BOARD_LEFT-2) {
13633                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13634                     boards[0][y][1] = 0;
13635                 } else
13636                 if(x == BOARD_RGHT+1) {
13637                     if(y >= gameInfo.holdingsSize) break;
13638                     boards[0][y][BOARD_WIDTH-2] = 0;
13639                 } else break;
13640             }
13641             boards[0][y][x] = EmptySquare;
13642             DrawPosition(FALSE, boards[0]);
13643         }
13644         break;
13645
13646       case PromotePiece:
13647         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13648            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13649             selection = (ChessSquare) (PROMOTED piece);
13650         } else if(piece == EmptySquare) selection = WhiteSilver;
13651         else selection = (ChessSquare)((int)piece - 1);
13652         goto defaultlabel;
13653
13654       case DemotePiece:
13655         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13656            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13657             selection = (ChessSquare) (DEMOTED piece);
13658         } else if(piece == EmptySquare) selection = BlackSilver;
13659         else selection = (ChessSquare)((int)piece + 1);
13660         goto defaultlabel;
13661
13662       case WhiteQueen:
13663       case BlackQueen:
13664         if(gameInfo.variant == VariantShatranj ||
13665            gameInfo.variant == VariantXiangqi  ||
13666            gameInfo.variant == VariantCourier  ||
13667            gameInfo.variant == VariantMakruk     )
13668             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13669         goto defaultlabel;
13670
13671       case WhiteKing:
13672       case BlackKing:
13673         if(gameInfo.variant == VariantXiangqi)
13674             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13675         if(gameInfo.variant == VariantKnightmate)
13676             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13677       default:
13678         defaultlabel:
13679         if (gameMode == IcsExamining) {
13680             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13681             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13682                      PieceToChar(selection), AAA + x, ONE + y);
13683             SendToICS(buf);
13684         } else {
13685             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13686                 int n;
13687                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13688                     n = PieceToNumber(selection - BlackPawn);
13689                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13690                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13691                     boards[0][BOARD_HEIGHT-1-n][1]++;
13692                 } else
13693                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13694                     n = PieceToNumber(selection);
13695                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13696                     boards[0][n][BOARD_WIDTH-1] = selection;
13697                     boards[0][n][BOARD_WIDTH-2]++;
13698                 }
13699             } else
13700             boards[0][y][x] = selection;
13701             DrawPosition(TRUE, boards[0]);
13702         }
13703         break;
13704     }
13705 }
13706
13707
13708 void
13709 DropMenuEvent(selection, x, y)
13710      ChessSquare selection;
13711      int x, y;
13712 {
13713     ChessMove moveType;
13714
13715     switch (gameMode) {
13716       case IcsPlayingWhite:
13717       case MachinePlaysBlack:
13718         if (!WhiteOnMove(currentMove)) {
13719             DisplayMoveError(_("It is Black's turn"));
13720             return;
13721         }
13722         moveType = WhiteDrop;
13723         break;
13724       case IcsPlayingBlack:
13725       case MachinePlaysWhite:
13726         if (WhiteOnMove(currentMove)) {
13727             DisplayMoveError(_("It is White's turn"));
13728             return;
13729         }
13730         moveType = BlackDrop;
13731         break;
13732       case EditGame:
13733         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13734         break;
13735       default:
13736         return;
13737     }
13738
13739     if (moveType == BlackDrop && selection < BlackPawn) {
13740       selection = (ChessSquare) ((int) selection
13741                                  + (int) BlackPawn - (int) WhitePawn);
13742     }
13743     if (boards[currentMove][y][x] != EmptySquare) {
13744         DisplayMoveError(_("That square is occupied"));
13745         return;
13746     }
13747
13748     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13749 }
13750
13751 void
13752 AcceptEvent()
13753 {
13754     /* Accept a pending offer of any kind from opponent */
13755
13756     if (appData.icsActive) {
13757         SendToICS(ics_prefix);
13758         SendToICS("accept\n");
13759     } else if (cmailMsgLoaded) {
13760         if (currentMove == cmailOldMove &&
13761             commentList[cmailOldMove] != NULL &&
13762             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13763                    "Black offers a draw" : "White offers a draw")) {
13764             TruncateGame();
13765             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13766             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13767         } else {
13768             DisplayError(_("There is no pending offer on this move"), 0);
13769             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13770         }
13771     } else {
13772         /* Not used for offers from chess program */
13773     }
13774 }
13775
13776 void
13777 DeclineEvent()
13778 {
13779     /* Decline a pending offer of any kind from opponent */
13780
13781     if (appData.icsActive) {
13782         SendToICS(ics_prefix);
13783         SendToICS("decline\n");
13784     } else if (cmailMsgLoaded) {
13785         if (currentMove == cmailOldMove &&
13786             commentList[cmailOldMove] != NULL &&
13787             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13788                    "Black offers a draw" : "White offers a draw")) {
13789 #ifdef NOTDEF
13790             AppendComment(cmailOldMove, "Draw declined", TRUE);
13791             DisplayComment(cmailOldMove - 1, "Draw declined");
13792 #endif /*NOTDEF*/
13793         } else {
13794             DisplayError(_("There is no pending offer on this move"), 0);
13795         }
13796     } else {
13797         /* Not used for offers from chess program */
13798     }
13799 }
13800
13801 void
13802 RematchEvent()
13803 {
13804     /* Issue ICS rematch command */
13805     if (appData.icsActive) {
13806         SendToICS(ics_prefix);
13807         SendToICS("rematch\n");
13808     }
13809 }
13810
13811 void
13812 CallFlagEvent()
13813 {
13814     /* Call your opponent's flag (claim a win on time) */
13815     if (appData.icsActive) {
13816         SendToICS(ics_prefix);
13817         SendToICS("flag\n");
13818     } else {
13819         switch (gameMode) {
13820           default:
13821             return;
13822           case MachinePlaysWhite:
13823             if (whiteFlag) {
13824                 if (blackFlag)
13825                   GameEnds(GameIsDrawn, "Both players ran out of time",
13826                            GE_PLAYER);
13827                 else
13828                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13829             } else {
13830                 DisplayError(_("Your opponent is not out of time"), 0);
13831             }
13832             break;
13833           case MachinePlaysBlack:
13834             if (blackFlag) {
13835                 if (whiteFlag)
13836                   GameEnds(GameIsDrawn, "Both players ran out of time",
13837                            GE_PLAYER);
13838                 else
13839                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13840             } else {
13841                 DisplayError(_("Your opponent is not out of time"), 0);
13842             }
13843             break;
13844         }
13845     }
13846 }
13847
13848 void
13849 ClockClick(int which)
13850 {       // [HGM] code moved to back-end from winboard.c
13851         if(which) { // black clock
13852           if (gameMode == EditPosition || gameMode == IcsExamining) {
13853             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13854             SetBlackToPlayEvent();
13855           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13856           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13857           } else if (shiftKey) {
13858             AdjustClock(which, -1);
13859           } else if (gameMode == IcsPlayingWhite ||
13860                      gameMode == MachinePlaysBlack) {
13861             CallFlagEvent();
13862           }
13863         } else { // white clock
13864           if (gameMode == EditPosition || gameMode == IcsExamining) {
13865             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13866             SetWhiteToPlayEvent();
13867           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13868           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13869           } else if (shiftKey) {
13870             AdjustClock(which, -1);
13871           } else if (gameMode == IcsPlayingBlack ||
13872                    gameMode == MachinePlaysWhite) {
13873             CallFlagEvent();
13874           }
13875         }
13876 }
13877
13878 void
13879 DrawEvent()
13880 {
13881     /* Offer draw or accept pending draw offer from opponent */
13882
13883     if (appData.icsActive) {
13884         /* Note: tournament rules require draw offers to be
13885            made after you make your move but before you punch
13886            your clock.  Currently ICS doesn't let you do that;
13887            instead, you immediately punch your clock after making
13888            a move, but you can offer a draw at any time. */
13889
13890         SendToICS(ics_prefix);
13891         SendToICS("draw\n");
13892         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13893     } else if (cmailMsgLoaded) {
13894         if (currentMove == cmailOldMove &&
13895             commentList[cmailOldMove] != NULL &&
13896             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13897                    "Black offers a draw" : "White offers a draw")) {
13898             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13899             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13900         } else if (currentMove == cmailOldMove + 1) {
13901             char *offer = WhiteOnMove(cmailOldMove) ?
13902               "White offers a draw" : "Black offers a draw";
13903             AppendComment(currentMove, offer, TRUE);
13904             DisplayComment(currentMove - 1, offer);
13905             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13906         } else {
13907             DisplayError(_("You must make your move before offering a draw"), 0);
13908             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13909         }
13910     } else if (first.offeredDraw) {
13911         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13912     } else {
13913         if (first.sendDrawOffers) {
13914             SendToProgram("draw\n", &first);
13915             userOfferedDraw = TRUE;
13916         }
13917     }
13918 }
13919
13920 void
13921 AdjournEvent()
13922 {
13923     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13924
13925     if (appData.icsActive) {
13926         SendToICS(ics_prefix);
13927         SendToICS("adjourn\n");
13928     } else {
13929         /* Currently GNU Chess doesn't offer or accept Adjourns */
13930     }
13931 }
13932
13933
13934 void
13935 AbortEvent()
13936 {
13937     /* Offer Abort or accept pending Abort offer from opponent */
13938
13939     if (appData.icsActive) {
13940         SendToICS(ics_prefix);
13941         SendToICS("abort\n");
13942     } else {
13943         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13944     }
13945 }
13946
13947 void
13948 ResignEvent()
13949 {
13950     /* Resign.  You can do this even if it's not your turn. */
13951
13952     if (appData.icsActive) {
13953         SendToICS(ics_prefix);
13954         SendToICS("resign\n");
13955     } else {
13956         switch (gameMode) {
13957           case MachinePlaysWhite:
13958             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13959             break;
13960           case MachinePlaysBlack:
13961             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13962             break;
13963           case EditGame:
13964             if (cmailMsgLoaded) {
13965                 TruncateGame();
13966                 if (WhiteOnMove(cmailOldMove)) {
13967                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13968                 } else {
13969                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13970                 }
13971                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13972             }
13973             break;
13974           default:
13975             break;
13976         }
13977     }
13978 }
13979
13980
13981 void
13982 StopObservingEvent()
13983 {
13984     /* Stop observing current games */
13985     SendToICS(ics_prefix);
13986     SendToICS("unobserve\n");
13987 }
13988
13989 void
13990 StopExaminingEvent()
13991 {
13992     /* Stop observing current game */
13993     SendToICS(ics_prefix);
13994     SendToICS("unexamine\n");
13995 }
13996
13997 void
13998 ForwardInner(target)
13999      int target;
14000 {
14001     int limit;
14002
14003     if (appData.debugMode)
14004         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14005                 target, currentMove, forwardMostMove);
14006
14007     if (gameMode == EditPosition)
14008       return;
14009
14010     if (gameMode == PlayFromGameFile && !pausing)
14011       PauseEvent();
14012
14013     if (gameMode == IcsExamining && pausing)
14014       limit = pauseExamForwardMostMove;
14015     else
14016       limit = forwardMostMove;
14017
14018     if (target > limit) target = limit;
14019
14020     if (target > 0 && moveList[target - 1][0]) {
14021         int fromX, fromY, toX, toY;
14022         toX = moveList[target - 1][2] - AAA;
14023         toY = moveList[target - 1][3] - ONE;
14024         if (moveList[target - 1][1] == '@') {
14025             if (appData.highlightLastMove) {
14026                 SetHighlights(-1, -1, toX, toY);
14027             }
14028         } else {
14029             fromX = moveList[target - 1][0] - AAA;
14030             fromY = moveList[target - 1][1] - ONE;
14031             if (target == currentMove + 1) {
14032                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14033             }
14034             if (appData.highlightLastMove) {
14035                 SetHighlights(fromX, fromY, toX, toY);
14036             }
14037         }
14038     }
14039     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14040         gameMode == Training || gameMode == PlayFromGameFile ||
14041         gameMode == AnalyzeFile) {
14042         while (currentMove < target) {
14043             SendMoveToProgram(currentMove++, &first);
14044         }
14045     } else {
14046         currentMove = target;
14047     }
14048
14049     if (gameMode == EditGame || gameMode == EndOfGame) {
14050         whiteTimeRemaining = timeRemaining[0][currentMove];
14051         blackTimeRemaining = timeRemaining[1][currentMove];
14052     }
14053     DisplayBothClocks();
14054     DisplayMove(currentMove - 1);
14055     DrawPosition(FALSE, boards[currentMove]);
14056     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14057     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14058         DisplayComment(currentMove - 1, commentList[currentMove]);
14059     }
14060 }
14061
14062
14063 void
14064 ForwardEvent()
14065 {
14066     if (gameMode == IcsExamining && !pausing) {
14067         SendToICS(ics_prefix);
14068         SendToICS("forward\n");
14069     } else {
14070         ForwardInner(currentMove + 1);
14071     }
14072 }
14073
14074 void
14075 ToEndEvent()
14076 {
14077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14078         /* to optimze, we temporarily turn off analysis mode while we feed
14079          * the remaining moves to the engine. Otherwise we get analysis output
14080          * after each move.
14081          */
14082         if (first.analysisSupport) {
14083           SendToProgram("exit\nforce\n", &first);
14084           first.analyzing = FALSE;
14085         }
14086     }
14087
14088     if (gameMode == IcsExamining && !pausing) {
14089         SendToICS(ics_prefix);
14090         SendToICS("forward 999999\n");
14091     } else {
14092         ForwardInner(forwardMostMove);
14093     }
14094
14095     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14096         /* we have fed all the moves, so reactivate analysis mode */
14097         SendToProgram("analyze\n", &first);
14098         first.analyzing = TRUE;
14099         /*first.maybeThinking = TRUE;*/
14100         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14101     }
14102 }
14103
14104 void
14105 BackwardInner(target)
14106      int target;
14107 {
14108     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14109
14110     if (appData.debugMode)
14111         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14112                 target, currentMove, forwardMostMove);
14113
14114     if (gameMode == EditPosition) return;
14115     if (currentMove <= backwardMostMove) {
14116         ClearHighlights();
14117         DrawPosition(full_redraw, boards[currentMove]);
14118         return;
14119     }
14120     if (gameMode == PlayFromGameFile && !pausing)
14121       PauseEvent();
14122
14123     if (moveList[target][0]) {
14124         int fromX, fromY, toX, toY;
14125         toX = moveList[target][2] - AAA;
14126         toY = moveList[target][3] - ONE;
14127         if (moveList[target][1] == '@') {
14128             if (appData.highlightLastMove) {
14129                 SetHighlights(-1, -1, toX, toY);
14130             }
14131         } else {
14132             fromX = moveList[target][0] - AAA;
14133             fromY = moveList[target][1] - ONE;
14134             if (target == currentMove - 1) {
14135                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14136             }
14137             if (appData.highlightLastMove) {
14138                 SetHighlights(fromX, fromY, toX, toY);
14139             }
14140         }
14141     }
14142     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14143         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14144         while (currentMove > target) {
14145             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14146                 // null move cannot be undone. Reload program with move history before it.
14147                 int i;
14148                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14149                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14150                 }
14151                 SendBoard(&first, i); 
14152                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14153                 break;
14154             }
14155             SendToProgram("undo\n", &first);
14156             currentMove--;
14157         }
14158     } else {
14159         currentMove = target;
14160     }
14161
14162     if (gameMode == EditGame || gameMode == EndOfGame) {
14163         whiteTimeRemaining = timeRemaining[0][currentMove];
14164         blackTimeRemaining = timeRemaining[1][currentMove];
14165     }
14166     DisplayBothClocks();
14167     DisplayMove(currentMove - 1);
14168     DrawPosition(full_redraw, boards[currentMove]);
14169     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14170     // [HGM] PV info: routine tests if comment empty
14171     DisplayComment(currentMove - 1, commentList[currentMove]);
14172 }
14173
14174 void
14175 BackwardEvent()
14176 {
14177     if (gameMode == IcsExamining && !pausing) {
14178         SendToICS(ics_prefix);
14179         SendToICS("backward\n");
14180     } else {
14181         BackwardInner(currentMove - 1);
14182     }
14183 }
14184
14185 void
14186 ToStartEvent()
14187 {
14188     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14189         /* to optimize, we temporarily turn off analysis mode while we undo
14190          * all the moves. Otherwise we get analysis output after each undo.
14191          */
14192         if (first.analysisSupport) {
14193           SendToProgram("exit\nforce\n", &first);
14194           first.analyzing = FALSE;
14195         }
14196     }
14197
14198     if (gameMode == IcsExamining && !pausing) {
14199         SendToICS(ics_prefix);
14200         SendToICS("backward 999999\n");
14201     } else {
14202         BackwardInner(backwardMostMove);
14203     }
14204
14205     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14206         /* we have fed all the moves, so reactivate analysis mode */
14207         SendToProgram("analyze\n", &first);
14208         first.analyzing = TRUE;
14209         /*first.maybeThinking = TRUE;*/
14210         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14211     }
14212 }
14213
14214 void
14215 ToNrEvent(int to)
14216 {
14217   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14218   if (to >= forwardMostMove) to = forwardMostMove;
14219   if (to <= backwardMostMove) to = backwardMostMove;
14220   if (to < currentMove) {
14221     BackwardInner(to);
14222   } else {
14223     ForwardInner(to);
14224   }
14225 }
14226
14227 void
14228 RevertEvent(Boolean annotate)
14229 {
14230     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14231         return;
14232     }
14233     if (gameMode != IcsExamining) {
14234         DisplayError(_("You are not examining a game"), 0);
14235         return;
14236     }
14237     if (pausing) {
14238         DisplayError(_("You can't revert while pausing"), 0);
14239         return;
14240     }
14241     SendToICS(ics_prefix);
14242     SendToICS("revert\n");
14243 }
14244
14245 void
14246 RetractMoveEvent()
14247 {
14248     switch (gameMode) {
14249       case MachinePlaysWhite:
14250       case MachinePlaysBlack:
14251         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14252             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14253             return;
14254         }
14255         if (forwardMostMove < 2) return;
14256         currentMove = forwardMostMove = forwardMostMove - 2;
14257         whiteTimeRemaining = timeRemaining[0][currentMove];
14258         blackTimeRemaining = timeRemaining[1][currentMove];
14259         DisplayBothClocks();
14260         DisplayMove(currentMove - 1);
14261         ClearHighlights();/*!! could figure this out*/
14262         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14263         SendToProgram("remove\n", &first);
14264         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14265         break;
14266
14267       case BeginningOfGame:
14268       default:
14269         break;
14270
14271       case IcsPlayingWhite:
14272       case IcsPlayingBlack:
14273         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14274             SendToICS(ics_prefix);
14275             SendToICS("takeback 2\n");
14276         } else {
14277             SendToICS(ics_prefix);
14278             SendToICS("takeback 1\n");
14279         }
14280         break;
14281     }
14282 }
14283
14284 void
14285 MoveNowEvent()
14286 {
14287     ChessProgramState *cps;
14288
14289     switch (gameMode) {
14290       case MachinePlaysWhite:
14291         if (!WhiteOnMove(forwardMostMove)) {
14292             DisplayError(_("It is your turn"), 0);
14293             return;
14294         }
14295         cps = &first;
14296         break;
14297       case MachinePlaysBlack:
14298         if (WhiteOnMove(forwardMostMove)) {
14299             DisplayError(_("It is your turn"), 0);
14300             return;
14301         }
14302         cps = &first;
14303         break;
14304       case TwoMachinesPlay:
14305         if (WhiteOnMove(forwardMostMove) ==
14306             (first.twoMachinesColor[0] == 'w')) {
14307             cps = &first;
14308         } else {
14309             cps = &second;
14310         }
14311         break;
14312       case BeginningOfGame:
14313       default:
14314         return;
14315     }
14316     SendToProgram("?\n", cps);
14317 }
14318
14319 void
14320 TruncateGameEvent()
14321 {
14322     EditGameEvent();
14323     if (gameMode != EditGame) return;
14324     TruncateGame();
14325 }
14326
14327 void
14328 TruncateGame()
14329 {
14330     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14331     if (forwardMostMove > currentMove) {
14332         if (gameInfo.resultDetails != NULL) {
14333             free(gameInfo.resultDetails);
14334             gameInfo.resultDetails = NULL;
14335             gameInfo.result = GameUnfinished;
14336         }
14337         forwardMostMove = currentMove;
14338         HistorySet(parseList, backwardMostMove, forwardMostMove,
14339                    currentMove-1);
14340     }
14341 }
14342
14343 void
14344 HintEvent()
14345 {
14346     if (appData.noChessProgram) return;
14347     switch (gameMode) {
14348       case MachinePlaysWhite:
14349         if (WhiteOnMove(forwardMostMove)) {
14350             DisplayError(_("Wait until your turn"), 0);
14351             return;
14352         }
14353         break;
14354       case BeginningOfGame:
14355       case MachinePlaysBlack:
14356         if (!WhiteOnMove(forwardMostMove)) {
14357             DisplayError(_("Wait until your turn"), 0);
14358             return;
14359         }
14360         break;
14361       default:
14362         DisplayError(_("No hint available"), 0);
14363         return;
14364     }
14365     SendToProgram("hint\n", &first);
14366     hintRequested = TRUE;
14367 }
14368
14369 void
14370 BookEvent()
14371 {
14372     if (appData.noChessProgram) return;
14373     switch (gameMode) {
14374       case MachinePlaysWhite:
14375         if (WhiteOnMove(forwardMostMove)) {
14376             DisplayError(_("Wait until your turn"), 0);
14377             return;
14378         }
14379         break;
14380       case BeginningOfGame:
14381       case MachinePlaysBlack:
14382         if (!WhiteOnMove(forwardMostMove)) {
14383             DisplayError(_("Wait until your turn"), 0);
14384             return;
14385         }
14386         break;
14387       case EditPosition:
14388         EditPositionDone(TRUE);
14389         break;
14390       case TwoMachinesPlay:
14391         return;
14392       default:
14393         break;
14394     }
14395     SendToProgram("bk\n", &first);
14396     bookOutput[0] = NULLCHAR;
14397     bookRequested = TRUE;
14398 }
14399
14400 void
14401 AboutGameEvent()
14402 {
14403     char *tags = PGNTags(&gameInfo);
14404     TagsPopUp(tags, CmailMsg());
14405     free(tags);
14406 }
14407
14408 /* end button procedures */
14409
14410 void
14411 PrintPosition(fp, move)
14412      FILE *fp;
14413      int move;
14414 {
14415     int i, j;
14416
14417     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14418         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14419             char c = PieceToChar(boards[move][i][j]);
14420             fputc(c == 'x' ? '.' : c, fp);
14421             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14422         }
14423     }
14424     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14425       fprintf(fp, "white to play\n");
14426     else
14427       fprintf(fp, "black to play\n");
14428 }
14429
14430 void
14431 PrintOpponents(fp)
14432      FILE *fp;
14433 {
14434     if (gameInfo.white != NULL) {
14435         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14436     } else {
14437         fprintf(fp, "\n");
14438     }
14439 }
14440
14441 /* Find last component of program's own name, using some heuristics */
14442 void
14443 TidyProgramName(prog, host, buf)
14444      char *prog, *host, buf[MSG_SIZ];
14445 {
14446     char *p, *q;
14447     int local = (strcmp(host, "localhost") == 0);
14448     while (!local && (p = strchr(prog, ';')) != NULL) {
14449         p++;
14450         while (*p == ' ') p++;
14451         prog = p;
14452     }
14453     if (*prog == '"' || *prog == '\'') {
14454         q = strchr(prog + 1, *prog);
14455     } else {
14456         q = strchr(prog, ' ');
14457     }
14458     if (q == NULL) q = prog + strlen(prog);
14459     p = q;
14460     while (p >= prog && *p != '/' && *p != '\\') p--;
14461     p++;
14462     if(p == prog && *p == '"') p++;
14463     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14464     memcpy(buf, p, q - p);
14465     buf[q - p] = NULLCHAR;
14466     if (!local) {
14467         strcat(buf, "@");
14468         strcat(buf, host);
14469     }
14470 }
14471
14472 char *
14473 TimeControlTagValue()
14474 {
14475     char buf[MSG_SIZ];
14476     if (!appData.clockMode) {
14477       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14478     } else if (movesPerSession > 0) {
14479       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14480     } else if (timeIncrement == 0) {
14481       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14482     } else {
14483       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14484     }
14485     return StrSave(buf);
14486 }
14487
14488 void
14489 SetGameInfo()
14490 {
14491     /* This routine is used only for certain modes */
14492     VariantClass v = gameInfo.variant;
14493     ChessMove r = GameUnfinished;
14494     char *p = NULL;
14495
14496     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14497         r = gameInfo.result;
14498         p = gameInfo.resultDetails;
14499         gameInfo.resultDetails = NULL;
14500     }
14501     ClearGameInfo(&gameInfo);
14502     gameInfo.variant = v;
14503
14504     switch (gameMode) {
14505       case MachinePlaysWhite:
14506         gameInfo.event = StrSave( appData.pgnEventHeader );
14507         gameInfo.site = StrSave(HostName());
14508         gameInfo.date = PGNDate();
14509         gameInfo.round = StrSave("-");
14510         gameInfo.white = StrSave(first.tidy);
14511         gameInfo.black = StrSave(UserName());
14512         gameInfo.timeControl = TimeControlTagValue();
14513         break;
14514
14515       case MachinePlaysBlack:
14516         gameInfo.event = StrSave( appData.pgnEventHeader );
14517         gameInfo.site = StrSave(HostName());
14518         gameInfo.date = PGNDate();
14519         gameInfo.round = StrSave("-");
14520         gameInfo.white = StrSave(UserName());
14521         gameInfo.black = StrSave(first.tidy);
14522         gameInfo.timeControl = TimeControlTagValue();
14523         break;
14524
14525       case TwoMachinesPlay:
14526         gameInfo.event = StrSave( appData.pgnEventHeader );
14527         gameInfo.site = StrSave(HostName());
14528         gameInfo.date = PGNDate();
14529         if (roundNr > 0) {
14530             char buf[MSG_SIZ];
14531             snprintf(buf, MSG_SIZ, "%d", roundNr);
14532             gameInfo.round = StrSave(buf);
14533         } else {
14534             gameInfo.round = StrSave("-");
14535         }
14536         if (first.twoMachinesColor[0] == 'w') {
14537             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14538             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14539         } else {
14540             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14541             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14542         }
14543         gameInfo.timeControl = TimeControlTagValue();
14544         break;
14545
14546       case EditGame:
14547         gameInfo.event = StrSave("Edited game");
14548         gameInfo.site = StrSave(HostName());
14549         gameInfo.date = PGNDate();
14550         gameInfo.round = StrSave("-");
14551         gameInfo.white = StrSave("-");
14552         gameInfo.black = StrSave("-");
14553         gameInfo.result = r;
14554         gameInfo.resultDetails = p;
14555         break;
14556
14557       case EditPosition:
14558         gameInfo.event = StrSave("Edited position");
14559         gameInfo.site = StrSave(HostName());
14560         gameInfo.date = PGNDate();
14561         gameInfo.round = StrSave("-");
14562         gameInfo.white = StrSave("-");
14563         gameInfo.black = StrSave("-");
14564         break;
14565
14566       case IcsPlayingWhite:
14567       case IcsPlayingBlack:
14568       case IcsObserving:
14569       case IcsExamining:
14570         break;
14571
14572       case PlayFromGameFile:
14573         gameInfo.event = StrSave("Game from non-PGN file");
14574         gameInfo.site = StrSave(HostName());
14575         gameInfo.date = PGNDate();
14576         gameInfo.round = StrSave("-");
14577         gameInfo.white = StrSave("?");
14578         gameInfo.black = StrSave("?");
14579         break;
14580
14581       default:
14582         break;
14583     }
14584 }
14585
14586 void
14587 ReplaceComment(index, text)
14588      int index;
14589      char *text;
14590 {
14591     int len;
14592     char *p;
14593     float score;
14594
14595     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14596        pvInfoList[index-1].depth == len &&
14597        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14598        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14599     while (*text == '\n') text++;
14600     len = strlen(text);
14601     while (len > 0 && text[len - 1] == '\n') len--;
14602
14603     if (commentList[index] != NULL)
14604       free(commentList[index]);
14605
14606     if (len == 0) {
14607         commentList[index] = NULL;
14608         return;
14609     }
14610   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14611       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14612       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14613     commentList[index] = (char *) malloc(len + 2);
14614     strncpy(commentList[index], text, len);
14615     commentList[index][len] = '\n';
14616     commentList[index][len + 1] = NULLCHAR;
14617   } else {
14618     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14619     char *p;
14620     commentList[index] = (char *) malloc(len + 7);
14621     safeStrCpy(commentList[index], "{\n", 3);
14622     safeStrCpy(commentList[index]+2, text, len+1);
14623     commentList[index][len+2] = NULLCHAR;
14624     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14625     strcat(commentList[index], "\n}\n");
14626   }
14627 }
14628
14629 void
14630 CrushCRs(text)
14631      char *text;
14632 {
14633   char *p = text;
14634   char *q = text;
14635   char ch;
14636
14637   do {
14638     ch = *p++;
14639     if (ch == '\r') continue;
14640     *q++ = ch;
14641   } while (ch != '\0');
14642 }
14643
14644 void
14645 AppendComment(index, text, addBraces)
14646      int index;
14647      char *text;
14648      Boolean addBraces; // [HGM] braces: tells if we should add {}
14649 {
14650     int oldlen, len;
14651     char *old;
14652
14653 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14654     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14655
14656     CrushCRs(text);
14657     while (*text == '\n') text++;
14658     len = strlen(text);
14659     while (len > 0 && text[len - 1] == '\n') len--;
14660
14661     if (len == 0) return;
14662
14663     if (commentList[index] != NULL) {
14664       Boolean addClosingBrace = addBraces;
14665         old = commentList[index];
14666         oldlen = strlen(old);
14667         while(commentList[index][oldlen-1] ==  '\n')
14668           commentList[index][--oldlen] = NULLCHAR;
14669         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14670         safeStrCpy(commentList[index], old, oldlen + len + 6);
14671         free(old);
14672         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14673         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14674           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14675           while (*text == '\n') { text++; len--; }
14676           commentList[index][--oldlen] = NULLCHAR;
14677       }
14678         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14679         else          strcat(commentList[index], "\n");
14680         strcat(commentList[index], text);
14681         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14682         else          strcat(commentList[index], "\n");
14683     } else {
14684         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14685         if(addBraces)
14686           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14687         else commentList[index][0] = NULLCHAR;
14688         strcat(commentList[index], text);
14689         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14690         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14691     }
14692 }
14693
14694 static char * FindStr( char * text, char * sub_text )
14695 {
14696     char * result = strstr( text, sub_text );
14697
14698     if( result != NULL ) {
14699         result += strlen( sub_text );
14700     }
14701
14702     return result;
14703 }
14704
14705 /* [AS] Try to extract PV info from PGN comment */
14706 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14707 char *GetInfoFromComment( int index, char * text )
14708 {
14709     char * sep = text, *p;
14710
14711     if( text != NULL && index > 0 ) {
14712         int score = 0;
14713         int depth = 0;
14714         int time = -1, sec = 0, deci;
14715         char * s_eval = FindStr( text, "[%eval " );
14716         char * s_emt = FindStr( text, "[%emt " );
14717
14718         if( s_eval != NULL || s_emt != NULL ) {
14719             /* New style */
14720             char delim;
14721
14722             if( s_eval != NULL ) {
14723                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14724                     return text;
14725                 }
14726
14727                 if( delim != ']' ) {
14728                     return text;
14729                 }
14730             }
14731
14732             if( s_emt != NULL ) {
14733             }
14734                 return text;
14735         }
14736         else {
14737             /* We expect something like: [+|-]nnn.nn/dd */
14738             int score_lo = 0;
14739
14740             if(*text != '{') return text; // [HGM] braces: must be normal comment
14741
14742             sep = strchr( text, '/' );
14743             if( sep == NULL || sep < (text+4) ) {
14744                 return text;
14745             }
14746
14747             p = text;
14748             if(p[1] == '(') { // comment starts with PV
14749                p = strchr(p, ')'); // locate end of PV
14750                if(p == NULL || sep < p+5) return text;
14751                // at this point we have something like "{(.*) +0.23/6 ..."
14752                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14753                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14754                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14755             }
14756             time = -1; sec = -1; deci = -1;
14757             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14758                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14759                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14760                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14761                 return text;
14762             }
14763
14764             if( score_lo < 0 || score_lo >= 100 ) {
14765                 return text;
14766             }
14767
14768             if(sec >= 0) time = 600*time + 10*sec; else
14769             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14770
14771             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14772
14773             /* [HGM] PV time: now locate end of PV info */
14774             while( *++sep >= '0' && *sep <= '9'); // strip depth
14775             if(time >= 0)
14776             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14777             if(sec >= 0)
14778             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14779             if(deci >= 0)
14780             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14781             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14782         }
14783
14784         if( depth <= 0 ) {
14785             return text;
14786         }
14787
14788         if( time < 0 ) {
14789             time = -1;
14790         }
14791
14792         pvInfoList[index-1].depth = depth;
14793         pvInfoList[index-1].score = score;
14794         pvInfoList[index-1].time  = 10*time; // centi-sec
14795         if(*sep == '}') *sep = 0; else *--sep = '{';
14796         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14797     }
14798     return sep;
14799 }
14800
14801 void
14802 SendToProgram(message, cps)
14803      char *message;
14804      ChessProgramState *cps;
14805 {
14806     int count, outCount, error;
14807     char buf[MSG_SIZ];
14808
14809     if (cps->pr == NULL) return;
14810     Attention(cps);
14811
14812     if (appData.debugMode) {
14813         TimeMark now;
14814         GetTimeMark(&now);
14815         fprintf(debugFP, "%ld >%-6s: %s",
14816                 SubtractTimeMarks(&now, &programStartTime),
14817                 cps->which, message);
14818     }
14819
14820     count = strlen(message);
14821     outCount = OutputToProcess(cps->pr, message, count, &error);
14822     if (outCount < count && !exiting
14823                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14824       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14825       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14826         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14827             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14828                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14829                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14830                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14831             } else {
14832                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14833                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14834                 gameInfo.result = res;
14835             }
14836             gameInfo.resultDetails = StrSave(buf);
14837         }
14838         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14839         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14840     }
14841 }
14842
14843 void
14844 ReceiveFromProgram(isr, closure, message, count, error)
14845      InputSourceRef isr;
14846      VOIDSTAR closure;
14847      char *message;
14848      int count;
14849      int error;
14850 {
14851     char *end_str;
14852     char buf[MSG_SIZ];
14853     ChessProgramState *cps = (ChessProgramState *)closure;
14854
14855     if (isr != cps->isr) return; /* Killed intentionally */
14856     if (count <= 0) {
14857         if (count == 0) {
14858             RemoveInputSource(cps->isr);
14859             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14860             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14861                     _(cps->which), cps->program);
14862         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14863                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14864                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14865                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14866                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14867                 } else {
14868                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14869                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14870                     gameInfo.result = res;
14871                 }
14872                 gameInfo.resultDetails = StrSave(buf);
14873             }
14874             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14875             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14876         } else {
14877             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14878                     _(cps->which), cps->program);
14879             RemoveInputSource(cps->isr);
14880
14881             /* [AS] Program is misbehaving badly... kill it */
14882             if( count == -2 ) {
14883                 DestroyChildProcess( cps->pr, 9 );
14884                 cps->pr = NoProc;
14885             }
14886
14887             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14888         }
14889         return;
14890     }
14891
14892     if ((end_str = strchr(message, '\r')) != NULL)
14893       *end_str = NULLCHAR;
14894     if ((end_str = strchr(message, '\n')) != NULL)
14895       *end_str = NULLCHAR;
14896
14897     if (appData.debugMode) {
14898         TimeMark now; int print = 1;
14899         char *quote = ""; char c; int i;
14900
14901         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14902                 char start = message[0];
14903                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14904                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14905                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14906                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14907                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14908                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14909                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14910                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14911                    sscanf(message, "hint: %c", &c)!=1 && 
14912                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14913                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14914                     print = (appData.engineComments >= 2);
14915                 }
14916                 message[0] = start; // restore original message
14917         }
14918         if(print) {
14919                 GetTimeMark(&now);
14920                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14921                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14922                         quote,
14923                         message);
14924         }
14925     }
14926
14927     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14928     if (appData.icsEngineAnalyze) {
14929         if (strstr(message, "whisper") != NULL ||
14930              strstr(message, "kibitz") != NULL ||
14931             strstr(message, "tellics") != NULL) return;
14932     }
14933
14934     HandleMachineMove(message, cps);
14935 }
14936
14937
14938 void
14939 SendTimeControl(cps, mps, tc, inc, sd, st)
14940      ChessProgramState *cps;
14941      int mps, inc, sd, st;
14942      long tc;
14943 {
14944     char buf[MSG_SIZ];
14945     int seconds;
14946
14947     if( timeControl_2 > 0 ) {
14948         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14949             tc = timeControl_2;
14950         }
14951     }
14952     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14953     inc /= cps->timeOdds;
14954     st  /= cps->timeOdds;
14955
14956     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14957
14958     if (st > 0) {
14959       /* Set exact time per move, normally using st command */
14960       if (cps->stKludge) {
14961         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14962         seconds = st % 60;
14963         if (seconds == 0) {
14964           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14965         } else {
14966           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14967         }
14968       } else {
14969         snprintf(buf, MSG_SIZ, "st %d\n", st);
14970       }
14971     } else {
14972       /* Set conventional or incremental time control, using level command */
14973       if (seconds == 0) {
14974         /* Note old gnuchess bug -- minutes:seconds used to not work.
14975            Fixed in later versions, but still avoid :seconds
14976            when seconds is 0. */
14977         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14978       } else {
14979         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14980                  seconds, inc/1000.);
14981       }
14982     }
14983     SendToProgram(buf, cps);
14984
14985     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14986     /* Orthogonally, limit search to given depth */
14987     if (sd > 0) {
14988       if (cps->sdKludge) {
14989         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14990       } else {
14991         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14992       }
14993       SendToProgram(buf, cps);
14994     }
14995
14996     if(cps->nps >= 0) { /* [HGM] nps */
14997         if(cps->supportsNPS == FALSE)
14998           cps->nps = -1; // don't use if engine explicitly says not supported!
14999         else {
15000           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15001           SendToProgram(buf, cps);
15002         }
15003     }
15004 }
15005
15006 ChessProgramState *WhitePlayer()
15007 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15008 {
15009     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15010        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15011         return &second;
15012     return &first;
15013 }
15014
15015 void
15016 SendTimeRemaining(cps, machineWhite)
15017      ChessProgramState *cps;
15018      int /*boolean*/ machineWhite;
15019 {
15020     char message[MSG_SIZ];
15021     long time, otime;
15022
15023     /* Note: this routine must be called when the clocks are stopped
15024        or when they have *just* been set or switched; otherwise
15025        it will be off by the time since the current tick started.
15026     */
15027     if (machineWhite) {
15028         time = whiteTimeRemaining / 10;
15029         otime = blackTimeRemaining / 10;
15030     } else {
15031         time = blackTimeRemaining / 10;
15032         otime = whiteTimeRemaining / 10;
15033     }
15034     /* [HGM] translate opponent's time by time-odds factor */
15035     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15036     if (appData.debugMode) {
15037         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15038     }
15039
15040     if (time <= 0) time = 1;
15041     if (otime <= 0) otime = 1;
15042
15043     snprintf(message, MSG_SIZ, "time %ld\n", time);
15044     SendToProgram(message, cps);
15045
15046     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15047     SendToProgram(message, cps);
15048 }
15049
15050 int
15051 BoolFeature(p, name, loc, cps)
15052      char **p;
15053      char *name;
15054      int *loc;
15055      ChessProgramState *cps;
15056 {
15057   char buf[MSG_SIZ];
15058   int len = strlen(name);
15059   int val;
15060
15061   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15062     (*p) += len + 1;
15063     sscanf(*p, "%d", &val);
15064     *loc = (val != 0);
15065     while (**p && **p != ' ')
15066       (*p)++;
15067     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15068     SendToProgram(buf, cps);
15069     return TRUE;
15070   }
15071   return FALSE;
15072 }
15073
15074 int
15075 IntFeature(p, name, loc, cps)
15076      char **p;
15077      char *name;
15078      int *loc;
15079      ChessProgramState *cps;
15080 {
15081   char buf[MSG_SIZ];
15082   int len = strlen(name);
15083   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15084     (*p) += len + 1;
15085     sscanf(*p, "%d", loc);
15086     while (**p && **p != ' ') (*p)++;
15087     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15088     SendToProgram(buf, cps);
15089     return TRUE;
15090   }
15091   return FALSE;
15092 }
15093
15094 int
15095 StringFeature(p, name, loc, cps)
15096      char **p;
15097      char *name;
15098      char loc[];
15099      ChessProgramState *cps;
15100 {
15101   char buf[MSG_SIZ];
15102   int len = strlen(name);
15103   if (strncmp((*p), name, len) == 0
15104       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15105     (*p) += len + 2;
15106     sscanf(*p, "%[^\"]", loc);
15107     while (**p && **p != '\"') (*p)++;
15108     if (**p == '\"') (*p)++;
15109     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15110     SendToProgram(buf, cps);
15111     return TRUE;
15112   }
15113   return FALSE;
15114 }
15115
15116 int
15117 ParseOption(Option *opt, ChessProgramState *cps)
15118 // [HGM] options: process the string that defines an engine option, and determine
15119 // name, type, default value, and allowed value range
15120 {
15121         char *p, *q, buf[MSG_SIZ];
15122         int n, min = (-1)<<31, max = 1<<31, def;
15123
15124         if(p = strstr(opt->name, " -spin ")) {
15125             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15126             if(max < min) max = min; // enforce consistency
15127             if(def < min) def = min;
15128             if(def > max) def = max;
15129             opt->value = def;
15130             opt->min = min;
15131             opt->max = max;
15132             opt->type = Spin;
15133         } else if((p = strstr(opt->name, " -slider "))) {
15134             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15135             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15136             if(max < min) max = min; // enforce consistency
15137             if(def < min) def = min;
15138             if(def > max) def = max;
15139             opt->value = def;
15140             opt->min = min;
15141             opt->max = max;
15142             opt->type = Spin; // Slider;
15143         } else if((p = strstr(opt->name, " -string "))) {
15144             opt->textValue = p+9;
15145             opt->type = TextBox;
15146         } else if((p = strstr(opt->name, " -file "))) {
15147             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15148             opt->textValue = p+7;
15149             opt->type = FileName; // FileName;
15150         } else if((p = strstr(opt->name, " -path "))) {
15151             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15152             opt->textValue = p+7;
15153             opt->type = PathName; // PathName;
15154         } else if(p = strstr(opt->name, " -check ")) {
15155             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15156             opt->value = (def != 0);
15157             opt->type = CheckBox;
15158         } else if(p = strstr(opt->name, " -combo ")) {
15159             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15160             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15161             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15162             opt->value = n = 0;
15163             while(q = StrStr(q, " /// ")) {
15164                 n++; *q = 0;    // count choices, and null-terminate each of them
15165                 q += 5;
15166                 if(*q == '*') { // remember default, which is marked with * prefix
15167                     q++;
15168                     opt->value = n;
15169                 }
15170                 cps->comboList[cps->comboCnt++] = q;
15171             }
15172             cps->comboList[cps->comboCnt++] = NULL;
15173             opt->max = n + 1;
15174             opt->type = ComboBox;
15175         } else if(p = strstr(opt->name, " -button")) {
15176             opt->type = Button;
15177         } else if(p = strstr(opt->name, " -save")) {
15178             opt->type = SaveButton;
15179         } else return FALSE;
15180         *p = 0; // terminate option name
15181         // now look if the command-line options define a setting for this engine option.
15182         if(cps->optionSettings && cps->optionSettings[0])
15183             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15184         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15185           snprintf(buf, MSG_SIZ, "option %s", p);
15186                 if(p = strstr(buf, ",")) *p = 0;
15187                 if(q = strchr(buf, '=')) switch(opt->type) {
15188                     case ComboBox:
15189                         for(n=0; n<opt->max; n++)
15190                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15191                         break;
15192                     case TextBox:
15193                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15194                         break;
15195                     case Spin:
15196                     case CheckBox:
15197                         opt->value = atoi(q+1);
15198                     default:
15199                         break;
15200                 }
15201                 strcat(buf, "\n");
15202                 SendToProgram(buf, cps);
15203         }
15204         return TRUE;
15205 }
15206
15207 void
15208 FeatureDone(cps, val)
15209      ChessProgramState* cps;
15210      int val;
15211 {
15212   DelayedEventCallback cb = GetDelayedEvent();
15213   if ((cb == InitBackEnd3 && cps == &first) ||
15214       (cb == SettingsMenuIfReady && cps == &second) ||
15215       (cb == LoadEngine) ||
15216       (cb == TwoMachinesEventIfReady)) {
15217     CancelDelayedEvent();
15218     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15219   }
15220   cps->initDone = val;
15221 }
15222
15223 /* Parse feature command from engine */
15224 void
15225 ParseFeatures(args, cps)
15226      char* args;
15227      ChessProgramState *cps;
15228 {
15229   char *p = args;
15230   char *q;
15231   int val;
15232   char buf[MSG_SIZ];
15233
15234   for (;;) {
15235     while (*p == ' ') p++;
15236     if (*p == NULLCHAR) return;
15237
15238     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15239     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15240     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15241     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15242     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15243     if (BoolFeature(&p, "reuse", &val, cps)) {
15244       /* Engine can disable reuse, but can't enable it if user said no */
15245       if (!val) cps->reuse = FALSE;
15246       continue;
15247     }
15248     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15249     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15250       if (gameMode == TwoMachinesPlay) {
15251         DisplayTwoMachinesTitle();
15252       } else {
15253         DisplayTitle("");
15254       }
15255       continue;
15256     }
15257     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15258     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15259     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15260     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15261     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15262     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15263     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15264     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15265     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15266     if (IntFeature(&p, "done", &val, cps)) {
15267       FeatureDone(cps, val);
15268       continue;
15269     }
15270     /* Added by Tord: */
15271     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15272     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15273     /* End of additions by Tord */
15274
15275     /* [HGM] added features: */
15276     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15277     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15278     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15279     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15280     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15281     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15282     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15283         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15284           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15285             SendToProgram(buf, cps);
15286             continue;
15287         }
15288         if(cps->nrOptions >= MAX_OPTIONS) {
15289             cps->nrOptions--;
15290             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15291             DisplayError(buf, 0);
15292         }
15293         continue;
15294     }
15295     /* End of additions by HGM */
15296
15297     /* unknown feature: complain and skip */
15298     q = p;
15299     while (*q && *q != '=') q++;
15300     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15301     SendToProgram(buf, cps);
15302     p = q;
15303     if (*p == '=') {
15304       p++;
15305       if (*p == '\"') {
15306         p++;
15307         while (*p && *p != '\"') p++;
15308         if (*p == '\"') p++;
15309       } else {
15310         while (*p && *p != ' ') p++;
15311       }
15312     }
15313   }
15314
15315 }
15316
15317 void
15318 PeriodicUpdatesEvent(newState)
15319      int newState;
15320 {
15321     if (newState == appData.periodicUpdates)
15322       return;
15323
15324     appData.periodicUpdates=newState;
15325
15326     /* Display type changes, so update it now */
15327 //    DisplayAnalysis();
15328
15329     /* Get the ball rolling again... */
15330     if (newState) {
15331         AnalysisPeriodicEvent(1);
15332         StartAnalysisClock();
15333     }
15334 }
15335
15336 void
15337 PonderNextMoveEvent(newState)
15338      int newState;
15339 {
15340     if (newState == appData.ponderNextMove) return;
15341     if (gameMode == EditPosition) EditPositionDone(TRUE);
15342     if (newState) {
15343         SendToProgram("hard\n", &first);
15344         if (gameMode == TwoMachinesPlay) {
15345             SendToProgram("hard\n", &second);
15346         }
15347     } else {
15348         SendToProgram("easy\n", &first);
15349         thinkOutput[0] = NULLCHAR;
15350         if (gameMode == TwoMachinesPlay) {
15351             SendToProgram("easy\n", &second);
15352         }
15353     }
15354     appData.ponderNextMove = newState;
15355 }
15356
15357 void
15358 NewSettingEvent(option, feature, command, value)
15359      char *command;
15360      int option, value, *feature;
15361 {
15362     char buf[MSG_SIZ];
15363
15364     if (gameMode == EditPosition) EditPositionDone(TRUE);
15365     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15366     if(feature == NULL || *feature) SendToProgram(buf, &first);
15367     if (gameMode == TwoMachinesPlay) {
15368         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15369     }
15370 }
15371
15372 void
15373 ShowThinkingEvent()
15374 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15375 {
15376     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15377     int newState = appData.showThinking
15378         // [HGM] thinking: other features now need thinking output as well
15379         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15380
15381     if (oldState == newState) return;
15382     oldState = newState;
15383     if (gameMode == EditPosition) EditPositionDone(TRUE);
15384     if (oldState) {
15385         SendToProgram("post\n", &first);
15386         if (gameMode == TwoMachinesPlay) {
15387             SendToProgram("post\n", &second);
15388         }
15389     } else {
15390         SendToProgram("nopost\n", &first);
15391         thinkOutput[0] = NULLCHAR;
15392         if (gameMode == TwoMachinesPlay) {
15393             SendToProgram("nopost\n", &second);
15394         }
15395     }
15396 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15397 }
15398
15399 void
15400 AskQuestionEvent(title, question, replyPrefix, which)
15401      char *title; char *question; char *replyPrefix; char *which;
15402 {
15403   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15404   if (pr == NoProc) return;
15405   AskQuestion(title, question, replyPrefix, pr);
15406 }
15407
15408 void
15409 TypeInEvent(char firstChar)
15410 {
15411     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15412         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15413         gameMode == AnalyzeMode || gameMode == EditGame || 
15414         gameMode == EditPosition || gameMode == IcsExamining ||
15415         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15416         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15417                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15418                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15419         gameMode == Training) PopUpMoveDialog(firstChar);
15420 }
15421
15422 void
15423 TypeInDoneEvent(char *move)
15424 {
15425         Board board;
15426         int n, fromX, fromY, toX, toY;
15427         char promoChar;
15428         ChessMove moveType;
15429
15430         // [HGM] FENedit
15431         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15432                 EditPositionPasteFEN(move);
15433                 return;
15434         }
15435         // [HGM] movenum: allow move number to be typed in any mode
15436         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15437           ToNrEvent(2*n-1);
15438           return;
15439         }
15440
15441       if (gameMode != EditGame && currentMove != forwardMostMove && 
15442         gameMode != Training) {
15443         DisplayMoveError(_("Displayed move is not current"));
15444       } else {
15445         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15446           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15447         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15448         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15449           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15450           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15451         } else {
15452           DisplayMoveError(_("Could not parse move"));
15453         }
15454       }
15455 }
15456
15457 void
15458 DisplayMove(moveNumber)
15459      int moveNumber;
15460 {
15461     char message[MSG_SIZ];
15462     char res[MSG_SIZ];
15463     char cpThinkOutput[MSG_SIZ];
15464
15465     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15466
15467     if (moveNumber == forwardMostMove - 1 ||
15468         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15469
15470         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15471
15472         if (strchr(cpThinkOutput, '\n')) {
15473             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15474         }
15475     } else {
15476         *cpThinkOutput = NULLCHAR;
15477     }
15478
15479     /* [AS] Hide thinking from human user */
15480     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15481         *cpThinkOutput = NULLCHAR;
15482         if( thinkOutput[0] != NULLCHAR ) {
15483             int i;
15484
15485             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15486                 cpThinkOutput[i] = '.';
15487             }
15488             cpThinkOutput[i] = NULLCHAR;
15489             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15490         }
15491     }
15492
15493     if (moveNumber == forwardMostMove - 1 &&
15494         gameInfo.resultDetails != NULL) {
15495         if (gameInfo.resultDetails[0] == NULLCHAR) {
15496           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15497         } else {
15498           snprintf(res, MSG_SIZ, " {%s} %s",
15499                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15500         }
15501     } else {
15502         res[0] = NULLCHAR;
15503     }
15504
15505     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15506         DisplayMessage(res, cpThinkOutput);
15507     } else {
15508       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15509                 WhiteOnMove(moveNumber) ? " " : ".. ",
15510                 parseList[moveNumber], res);
15511         DisplayMessage(message, cpThinkOutput);
15512     }
15513 }
15514
15515 void
15516 DisplayComment(moveNumber, text)
15517      int moveNumber;
15518      char *text;
15519 {
15520     char title[MSG_SIZ];
15521
15522     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15523       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15524     } else {
15525       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15526               WhiteOnMove(moveNumber) ? " " : ".. ",
15527               parseList[moveNumber]);
15528     }
15529     if (text != NULL && (appData.autoDisplayComment || commentUp))
15530         CommentPopUp(title, text);
15531 }
15532
15533 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15534  * might be busy thinking or pondering.  It can be omitted if your
15535  * gnuchess is configured to stop thinking immediately on any user
15536  * input.  However, that gnuchess feature depends on the FIONREAD
15537  * ioctl, which does not work properly on some flavors of Unix.
15538  */
15539 void
15540 Attention(cps)
15541      ChessProgramState *cps;
15542 {
15543 #if ATTENTION
15544     if (!cps->useSigint) return;
15545     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15546     switch (gameMode) {
15547       case MachinePlaysWhite:
15548       case MachinePlaysBlack:
15549       case TwoMachinesPlay:
15550       case IcsPlayingWhite:
15551       case IcsPlayingBlack:
15552       case AnalyzeMode:
15553       case AnalyzeFile:
15554         /* Skip if we know it isn't thinking */
15555         if (!cps->maybeThinking) return;
15556         if (appData.debugMode)
15557           fprintf(debugFP, "Interrupting %s\n", cps->which);
15558         InterruptChildProcess(cps->pr);
15559         cps->maybeThinking = FALSE;
15560         break;
15561       default:
15562         break;
15563     }
15564 #endif /*ATTENTION*/
15565 }
15566
15567 int
15568 CheckFlags()
15569 {
15570     if (whiteTimeRemaining <= 0) {
15571         if (!whiteFlag) {
15572             whiteFlag = TRUE;
15573             if (appData.icsActive) {
15574                 if (appData.autoCallFlag &&
15575                     gameMode == IcsPlayingBlack && !blackFlag) {
15576                   SendToICS(ics_prefix);
15577                   SendToICS("flag\n");
15578                 }
15579             } else {
15580                 if (blackFlag) {
15581                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15582                 } else {
15583                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15584                     if (appData.autoCallFlag) {
15585                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15586                         return TRUE;
15587                     }
15588                 }
15589             }
15590         }
15591     }
15592     if (blackTimeRemaining <= 0) {
15593         if (!blackFlag) {
15594             blackFlag = TRUE;
15595             if (appData.icsActive) {
15596                 if (appData.autoCallFlag &&
15597                     gameMode == IcsPlayingWhite && !whiteFlag) {
15598                   SendToICS(ics_prefix);
15599                   SendToICS("flag\n");
15600                 }
15601             } else {
15602                 if (whiteFlag) {
15603                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15604                 } else {
15605                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15606                     if (appData.autoCallFlag) {
15607                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15608                         return TRUE;
15609                     }
15610                 }
15611             }
15612         }
15613     }
15614     return FALSE;
15615 }
15616
15617 void
15618 CheckTimeControl()
15619 {
15620     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15621         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15622
15623     /*
15624      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15625      */
15626     if ( !WhiteOnMove(forwardMostMove) ) {
15627         /* White made time control */
15628         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15629         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15630         /* [HGM] time odds: correct new time quota for time odds! */
15631                                             / WhitePlayer()->timeOdds;
15632         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15633     } else {
15634         lastBlack -= blackTimeRemaining;
15635         /* Black made time control */
15636         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15637                                             / WhitePlayer()->other->timeOdds;
15638         lastWhite = whiteTimeRemaining;
15639     }
15640 }
15641
15642 void
15643 DisplayBothClocks()
15644 {
15645     int wom = gameMode == EditPosition ?
15646       !blackPlaysFirst : WhiteOnMove(currentMove);
15647     DisplayWhiteClock(whiteTimeRemaining, wom);
15648     DisplayBlackClock(blackTimeRemaining, !wom);
15649 }
15650
15651
15652 /* Timekeeping seems to be a portability nightmare.  I think everyone
15653    has ftime(), but I'm really not sure, so I'm including some ifdefs
15654    to use other calls if you don't.  Clocks will be less accurate if
15655    you have neither ftime nor gettimeofday.
15656 */
15657
15658 /* VS 2008 requires the #include outside of the function */
15659 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15660 #include <sys/timeb.h>
15661 #endif
15662
15663 /* Get the current time as a TimeMark */
15664 void
15665 GetTimeMark(tm)
15666      TimeMark *tm;
15667 {
15668 #if HAVE_GETTIMEOFDAY
15669
15670     struct timeval timeVal;
15671     struct timezone timeZone;
15672
15673     gettimeofday(&timeVal, &timeZone);
15674     tm->sec = (long) timeVal.tv_sec;
15675     tm->ms = (int) (timeVal.tv_usec / 1000L);
15676
15677 #else /*!HAVE_GETTIMEOFDAY*/
15678 #if HAVE_FTIME
15679
15680 // include <sys/timeb.h> / moved to just above start of function
15681     struct timeb timeB;
15682
15683     ftime(&timeB);
15684     tm->sec = (long) timeB.time;
15685     tm->ms = (int) timeB.millitm;
15686
15687 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15688     tm->sec = (long) time(NULL);
15689     tm->ms = 0;
15690 #endif
15691 #endif
15692 }
15693
15694 /* Return the difference in milliseconds between two
15695    time marks.  We assume the difference will fit in a long!
15696 */
15697 long
15698 SubtractTimeMarks(tm2, tm1)
15699      TimeMark *tm2, *tm1;
15700 {
15701     return 1000L*(tm2->sec - tm1->sec) +
15702            (long) (tm2->ms - tm1->ms);
15703 }
15704
15705
15706 /*
15707  * Code to manage the game clocks.
15708  *
15709  * In tournament play, black starts the clock and then white makes a move.
15710  * We give the human user a slight advantage if he is playing white---the
15711  * clocks don't run until he makes his first move, so it takes zero time.
15712  * Also, we don't account for network lag, so we could get out of sync
15713  * with GNU Chess's clock -- but then, referees are always right.
15714  */
15715
15716 static TimeMark tickStartTM;
15717 static long intendedTickLength;
15718
15719 long
15720 NextTickLength(timeRemaining)
15721      long timeRemaining;
15722 {
15723     long nominalTickLength, nextTickLength;
15724
15725     if (timeRemaining > 0L && timeRemaining <= 10000L)
15726       nominalTickLength = 100L;
15727     else
15728       nominalTickLength = 1000L;
15729     nextTickLength = timeRemaining % nominalTickLength;
15730     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15731
15732     return nextTickLength;
15733 }
15734
15735 /* Adjust clock one minute up or down */
15736 void
15737 AdjustClock(Boolean which, int dir)
15738 {
15739     if(which) blackTimeRemaining += 60000*dir;
15740     else      whiteTimeRemaining += 60000*dir;
15741     DisplayBothClocks();
15742 }
15743
15744 /* Stop clocks and reset to a fresh time control */
15745 void
15746 ResetClocks()
15747 {
15748     (void) StopClockTimer();
15749     if (appData.icsActive) {
15750         whiteTimeRemaining = blackTimeRemaining = 0;
15751     } else if (searchTime) {
15752         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15753         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15754     } else { /* [HGM] correct new time quote for time odds */
15755         whiteTC = blackTC = fullTimeControlString;
15756         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15757         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15758     }
15759     if (whiteFlag || blackFlag) {
15760         DisplayTitle("");
15761         whiteFlag = blackFlag = FALSE;
15762     }
15763     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15764     DisplayBothClocks();
15765 }
15766
15767 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15768
15769 /* Decrement running clock by amount of time that has passed */
15770 void
15771 DecrementClocks()
15772 {
15773     long timeRemaining;
15774     long lastTickLength, fudge;
15775     TimeMark now;
15776
15777     if (!appData.clockMode) return;
15778     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15779
15780     GetTimeMark(&now);
15781
15782     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15783
15784     /* Fudge if we woke up a little too soon */
15785     fudge = intendedTickLength - lastTickLength;
15786     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15787
15788     if (WhiteOnMove(forwardMostMove)) {
15789         if(whiteNPS >= 0) lastTickLength = 0;
15790         timeRemaining = whiteTimeRemaining -= lastTickLength;
15791         if(timeRemaining < 0 && !appData.icsActive) {
15792             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15793             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15794                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15795                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15796             }
15797         }
15798         DisplayWhiteClock(whiteTimeRemaining - fudge,
15799                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15800     } else {
15801         if(blackNPS >= 0) lastTickLength = 0;
15802         timeRemaining = blackTimeRemaining -= lastTickLength;
15803         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15804             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15805             if(suddenDeath) {
15806                 blackStartMove = forwardMostMove;
15807                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15808             }
15809         }
15810         DisplayBlackClock(blackTimeRemaining - fudge,
15811                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15812     }
15813     if (CheckFlags()) return;
15814
15815     tickStartTM = now;
15816     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15817     StartClockTimer(intendedTickLength);
15818
15819     /* if the time remaining has fallen below the alarm threshold, sound the
15820      * alarm. if the alarm has sounded and (due to a takeback or time control
15821      * with increment) the time remaining has increased to a level above the
15822      * threshold, reset the alarm so it can sound again.
15823      */
15824
15825     if (appData.icsActive && appData.icsAlarm) {
15826
15827         /* make sure we are dealing with the user's clock */
15828         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15829                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15830            )) return;
15831
15832         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15833             alarmSounded = FALSE;
15834         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15835             PlayAlarmSound();
15836             alarmSounded = TRUE;
15837         }
15838     }
15839 }
15840
15841
15842 /* A player has just moved, so stop the previously running
15843    clock and (if in clock mode) start the other one.
15844    We redisplay both clocks in case we're in ICS mode, because
15845    ICS gives us an update to both clocks after every move.
15846    Note that this routine is called *after* forwardMostMove
15847    is updated, so the last fractional tick must be subtracted
15848    from the color that is *not* on move now.
15849 */
15850 void
15851 SwitchClocks(int newMoveNr)
15852 {
15853     long lastTickLength;
15854     TimeMark now;
15855     int flagged = FALSE;
15856
15857     GetTimeMark(&now);
15858
15859     if (StopClockTimer() && appData.clockMode) {
15860         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15861         if (!WhiteOnMove(forwardMostMove)) {
15862             if(blackNPS >= 0) lastTickLength = 0;
15863             blackTimeRemaining -= lastTickLength;
15864            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15865 //         if(pvInfoList[forwardMostMove].time == -1)
15866                  pvInfoList[forwardMostMove].time =               // use GUI time
15867                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15868         } else {
15869            if(whiteNPS >= 0) lastTickLength = 0;
15870            whiteTimeRemaining -= lastTickLength;
15871            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15872 //         if(pvInfoList[forwardMostMove].time == -1)
15873                  pvInfoList[forwardMostMove].time =
15874                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15875         }
15876         flagged = CheckFlags();
15877     }
15878     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15879     CheckTimeControl();
15880
15881     if (flagged || !appData.clockMode) return;
15882
15883     switch (gameMode) {
15884       case MachinePlaysBlack:
15885       case MachinePlaysWhite:
15886       case BeginningOfGame:
15887         if (pausing) return;
15888         break;
15889
15890       case EditGame:
15891       case PlayFromGameFile:
15892       case IcsExamining:
15893         return;
15894
15895       default:
15896         break;
15897     }
15898
15899     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15900         if(WhiteOnMove(forwardMostMove))
15901              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15902         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15903     }
15904
15905     tickStartTM = now;
15906     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15907       whiteTimeRemaining : blackTimeRemaining);
15908     StartClockTimer(intendedTickLength);
15909 }
15910
15911
15912 /* Stop both clocks */
15913 void
15914 StopClocks()
15915 {
15916     long lastTickLength;
15917     TimeMark now;
15918
15919     if (!StopClockTimer()) return;
15920     if (!appData.clockMode) return;
15921
15922     GetTimeMark(&now);
15923
15924     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15925     if (WhiteOnMove(forwardMostMove)) {
15926         if(whiteNPS >= 0) lastTickLength = 0;
15927         whiteTimeRemaining -= lastTickLength;
15928         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15929     } else {
15930         if(blackNPS >= 0) lastTickLength = 0;
15931         blackTimeRemaining -= lastTickLength;
15932         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15933     }
15934     CheckFlags();
15935 }
15936
15937 /* Start clock of player on move.  Time may have been reset, so
15938    if clock is already running, stop and restart it. */
15939 void
15940 StartClocks()
15941 {
15942     (void) StopClockTimer(); /* in case it was running already */
15943     DisplayBothClocks();
15944     if (CheckFlags()) return;
15945
15946     if (!appData.clockMode) return;
15947     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15948
15949     GetTimeMark(&tickStartTM);
15950     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15951       whiteTimeRemaining : blackTimeRemaining);
15952
15953    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15954     whiteNPS = blackNPS = -1;
15955     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15956        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15957         whiteNPS = first.nps;
15958     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15959        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15960         blackNPS = first.nps;
15961     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15962         whiteNPS = second.nps;
15963     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15964         blackNPS = second.nps;
15965     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15966
15967     StartClockTimer(intendedTickLength);
15968 }
15969
15970 char *
15971 TimeString(ms)
15972      long ms;
15973 {
15974     long second, minute, hour, day;
15975     char *sign = "";
15976     static char buf[32];
15977
15978     if (ms > 0 && ms <= 9900) {
15979       /* convert milliseconds to tenths, rounding up */
15980       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15981
15982       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15983       return buf;
15984     }
15985
15986     /* convert milliseconds to seconds, rounding up */
15987     /* use floating point to avoid strangeness of integer division
15988        with negative dividends on many machines */
15989     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15990
15991     if (second < 0) {
15992         sign = "-";
15993         second = -second;
15994     }
15995
15996     day = second / (60 * 60 * 24);
15997     second = second % (60 * 60 * 24);
15998     hour = second / (60 * 60);
15999     second = second % (60 * 60);
16000     minute = second / 60;
16001     second = second % 60;
16002
16003     if (day > 0)
16004       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16005               sign, day, hour, minute, second);
16006     else if (hour > 0)
16007       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16008     else
16009       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16010
16011     return buf;
16012 }
16013
16014
16015 /*
16016  * This is necessary because some C libraries aren't ANSI C compliant yet.
16017  */
16018 char *
16019 StrStr(string, match)
16020      char *string, *match;
16021 {
16022     int i, length;
16023
16024     length = strlen(match);
16025
16026     for (i = strlen(string) - length; i >= 0; i--, string++)
16027       if (!strncmp(match, string, length))
16028         return string;
16029
16030     return NULL;
16031 }
16032
16033 char *
16034 StrCaseStr(string, match)
16035      char *string, *match;
16036 {
16037     int i, j, length;
16038
16039     length = strlen(match);
16040
16041     for (i = strlen(string) - length; i >= 0; i--, string++) {
16042         for (j = 0; j < length; j++) {
16043             if (ToLower(match[j]) != ToLower(string[j]))
16044               break;
16045         }
16046         if (j == length) return string;
16047     }
16048
16049     return NULL;
16050 }
16051
16052 #ifndef _amigados
16053 int
16054 StrCaseCmp(s1, s2)
16055      char *s1, *s2;
16056 {
16057     char c1, c2;
16058
16059     for (;;) {
16060         c1 = ToLower(*s1++);
16061         c2 = ToLower(*s2++);
16062         if (c1 > c2) return 1;
16063         if (c1 < c2) return -1;
16064         if (c1 == NULLCHAR) return 0;
16065     }
16066 }
16067
16068
16069 int
16070 ToLower(c)
16071      int c;
16072 {
16073     return isupper(c) ? tolower(c) : c;
16074 }
16075
16076
16077 int
16078 ToUpper(c)
16079      int c;
16080 {
16081     return islower(c) ? toupper(c) : c;
16082 }
16083 #endif /* !_amigados    */
16084
16085 char *
16086 StrSave(s)
16087      char *s;
16088 {
16089   char *ret;
16090
16091   if ((ret = (char *) malloc(strlen(s) + 1)))
16092     {
16093       safeStrCpy(ret, s, strlen(s)+1);
16094     }
16095   return ret;
16096 }
16097
16098 char *
16099 StrSavePtr(s, savePtr)
16100      char *s, **savePtr;
16101 {
16102     if (*savePtr) {
16103         free(*savePtr);
16104     }
16105     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16106       safeStrCpy(*savePtr, s, strlen(s)+1);
16107     }
16108     return(*savePtr);
16109 }
16110
16111 char *
16112 PGNDate()
16113 {
16114     time_t clock;
16115     struct tm *tm;
16116     char buf[MSG_SIZ];
16117
16118     clock = time((time_t *)NULL);
16119     tm = localtime(&clock);
16120     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16121             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16122     return StrSave(buf);
16123 }
16124
16125
16126 char *
16127 PositionToFEN(move, overrideCastling)
16128      int move;
16129      char *overrideCastling;
16130 {
16131     int i, j, fromX, fromY, toX, toY;
16132     int whiteToPlay;
16133     char buf[MSG_SIZ];
16134     char *p, *q;
16135     int emptycount;
16136     ChessSquare piece;
16137
16138     whiteToPlay = (gameMode == EditPosition) ?
16139       !blackPlaysFirst : (move % 2 == 0);
16140     p = buf;
16141
16142     /* Piece placement data */
16143     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16144         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16145         emptycount = 0;
16146         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16147             if (boards[move][i][j] == EmptySquare) {
16148                 emptycount++;
16149             } else { ChessSquare piece = boards[move][i][j];
16150                 if (emptycount > 0) {
16151                     if(emptycount<10) /* [HGM] can be >= 10 */
16152                         *p++ = '0' + emptycount;
16153                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16154                     emptycount = 0;
16155                 }
16156                 if(PieceToChar(piece) == '+') {
16157                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16158                     *p++ = '+';
16159                     piece = (ChessSquare)(DEMOTED piece);
16160                 }
16161                 *p++ = PieceToChar(piece);
16162                 if(p[-1] == '~') {
16163                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16164                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16165                     *p++ = '~';
16166                 }
16167             }
16168         }
16169         if (emptycount > 0) {
16170             if(emptycount<10) /* [HGM] can be >= 10 */
16171                 *p++ = '0' + emptycount;
16172             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16173             emptycount = 0;
16174         }
16175         *p++ = '/';
16176     }
16177     *(p - 1) = ' ';
16178
16179     /* [HGM] print Crazyhouse or Shogi holdings */
16180     if( gameInfo.holdingsWidth ) {
16181         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16182         q = p;
16183         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16184             piece = boards[move][i][BOARD_WIDTH-1];
16185             if( piece != EmptySquare )
16186               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16187                   *p++ = PieceToChar(piece);
16188         }
16189         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16190             piece = boards[move][BOARD_HEIGHT-i-1][0];
16191             if( piece != EmptySquare )
16192               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16193                   *p++ = PieceToChar(piece);
16194         }
16195
16196         if( q == p ) *p++ = '-';
16197         *p++ = ']';
16198         *p++ = ' ';
16199     }
16200
16201     /* Active color */
16202     *p++ = whiteToPlay ? 'w' : 'b';
16203     *p++ = ' ';
16204
16205   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16206     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16207   } else {
16208   if(nrCastlingRights) {
16209      q = p;
16210      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16211        /* [HGM] write directly from rights */
16212            if(boards[move][CASTLING][2] != NoRights &&
16213               boards[move][CASTLING][0] != NoRights   )
16214                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16215            if(boards[move][CASTLING][2] != NoRights &&
16216               boards[move][CASTLING][1] != NoRights   )
16217                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16218            if(boards[move][CASTLING][5] != NoRights &&
16219               boards[move][CASTLING][3] != NoRights   )
16220                 *p++ = boards[move][CASTLING][3] + AAA;
16221            if(boards[move][CASTLING][5] != NoRights &&
16222               boards[move][CASTLING][4] != NoRights   )
16223                 *p++ = boards[move][CASTLING][4] + AAA;
16224      } else {
16225
16226         /* [HGM] write true castling rights */
16227         if( nrCastlingRights == 6 ) {
16228             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16229                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16230             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16231                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16232             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16233                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16234             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16235                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16236         }
16237      }
16238      if (q == p) *p++ = '-'; /* No castling rights */
16239      *p++ = ' ';
16240   }
16241
16242   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16243      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16244     /* En passant target square */
16245     if (move > backwardMostMove) {
16246         fromX = moveList[move - 1][0] - AAA;
16247         fromY = moveList[move - 1][1] - ONE;
16248         toX = moveList[move - 1][2] - AAA;
16249         toY = moveList[move - 1][3] - ONE;
16250         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16251             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16252             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16253             fromX == toX) {
16254             /* 2-square pawn move just happened */
16255             *p++ = toX + AAA;
16256             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16257         } else {
16258             *p++ = '-';
16259         }
16260     } else if(move == backwardMostMove) {
16261         // [HGM] perhaps we should always do it like this, and forget the above?
16262         if((signed char)boards[move][EP_STATUS] >= 0) {
16263             *p++ = boards[move][EP_STATUS] + AAA;
16264             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16265         } else {
16266             *p++ = '-';
16267         }
16268     } else {
16269         *p++ = '-';
16270     }
16271     *p++ = ' ';
16272   }
16273   }
16274
16275     /* [HGM] find reversible plies */
16276     {   int i = 0, j=move;
16277
16278         if (appData.debugMode) { int k;
16279             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16280             for(k=backwardMostMove; k<=forwardMostMove; k++)
16281                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16282
16283         }
16284
16285         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16286         if( j == backwardMostMove ) i += initialRulePlies;
16287         sprintf(p, "%d ", i);
16288         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16289     }
16290     /* Fullmove number */
16291     sprintf(p, "%d", (move / 2) + 1);
16292
16293     return StrSave(buf);
16294 }
16295
16296 Boolean
16297 ParseFEN(board, blackPlaysFirst, fen)
16298     Board board;
16299      int *blackPlaysFirst;
16300      char *fen;
16301 {
16302     int i, j;
16303     char *p, c;
16304     int emptycount;
16305     ChessSquare piece;
16306
16307     p = fen;
16308
16309     /* [HGM] by default clear Crazyhouse holdings, if present */
16310     if(gameInfo.holdingsWidth) {
16311        for(i=0; i<BOARD_HEIGHT; i++) {
16312            board[i][0]             = EmptySquare; /* black holdings */
16313            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16314            board[i][1]             = (ChessSquare) 0; /* black counts */
16315            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16316        }
16317     }
16318
16319     /* Piece placement data */
16320     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16321         j = 0;
16322         for (;;) {
16323             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16324                 if (*p == '/') p++;
16325                 emptycount = gameInfo.boardWidth - j;
16326                 while (emptycount--)
16327                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16328                 break;
16329 #if(BOARD_FILES >= 10)
16330             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16331                 p++; emptycount=10;
16332                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16333                 while (emptycount--)
16334                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16335 #endif
16336             } else if (isdigit(*p)) {
16337                 emptycount = *p++ - '0';
16338                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16339                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16340                 while (emptycount--)
16341                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16342             } else if (*p == '+' || isalpha(*p)) {
16343                 if (j >= gameInfo.boardWidth) return FALSE;
16344                 if(*p=='+') {
16345                     piece = CharToPiece(*++p);
16346                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16347                     piece = (ChessSquare) (PROMOTED piece ); p++;
16348                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16349                 } else piece = CharToPiece(*p++);
16350
16351                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16352                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16353                     piece = (ChessSquare) (PROMOTED piece);
16354                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16355                     p++;
16356                 }
16357                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16358             } else {
16359                 return FALSE;
16360             }
16361         }
16362     }
16363     while (*p == '/' || *p == ' ') p++;
16364
16365     /* [HGM] look for Crazyhouse holdings here */
16366     while(*p==' ') p++;
16367     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16368         if(*p == '[') p++;
16369         if(*p == '-' ) p++; /* empty holdings */ else {
16370             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16371             /* if we would allow FEN reading to set board size, we would   */
16372             /* have to add holdings and shift the board read so far here   */
16373             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16374                 p++;
16375                 if((int) piece >= (int) BlackPawn ) {
16376                     i = (int)piece - (int)BlackPawn;
16377                     i = PieceToNumber((ChessSquare)i);
16378                     if( i >= gameInfo.holdingsSize ) return FALSE;
16379                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16380                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16381                 } else {
16382                     i = (int)piece - (int)WhitePawn;
16383                     i = PieceToNumber((ChessSquare)i);
16384                     if( i >= gameInfo.holdingsSize ) return FALSE;
16385                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16386                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16387                 }
16388             }
16389         }
16390         if(*p == ']') p++;
16391     }
16392
16393     while(*p == ' ') p++;
16394
16395     /* Active color */
16396     c = *p++;
16397     if(appData.colorNickNames) {
16398       if( c == appData.colorNickNames[0] ) c = 'w'; else
16399       if( c == appData.colorNickNames[1] ) c = 'b';
16400     }
16401     switch (c) {
16402       case 'w':
16403         *blackPlaysFirst = FALSE;
16404         break;
16405       case 'b':
16406         *blackPlaysFirst = TRUE;
16407         break;
16408       default:
16409         return FALSE;
16410     }
16411
16412     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16413     /* return the extra info in global variiables             */
16414
16415     /* set defaults in case FEN is incomplete */
16416     board[EP_STATUS] = EP_UNKNOWN;
16417     for(i=0; i<nrCastlingRights; i++ ) {
16418         board[CASTLING][i] =
16419             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16420     }   /* assume possible unless obviously impossible */
16421     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16422     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16423     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16424                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16425     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16426     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16427     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16428                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16429     FENrulePlies = 0;
16430
16431     while(*p==' ') p++;
16432     if(nrCastlingRights) {
16433       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16434           /* castling indicator present, so default becomes no castlings */
16435           for(i=0; i<nrCastlingRights; i++ ) {
16436                  board[CASTLING][i] = NoRights;
16437           }
16438       }
16439       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16440              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16441              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16442              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16443         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16444
16445         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16446             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16447             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16448         }
16449         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16450             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16451         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16452                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16453         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16454                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16455         switch(c) {
16456           case'K':
16457               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16458               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16459               board[CASTLING][2] = whiteKingFile;
16460               break;
16461           case'Q':
16462               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16463               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16464               board[CASTLING][2] = whiteKingFile;
16465               break;
16466           case'k':
16467               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16468               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16469               board[CASTLING][5] = blackKingFile;
16470               break;
16471           case'q':
16472               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16473               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16474               board[CASTLING][5] = blackKingFile;
16475           case '-':
16476               break;
16477           default: /* FRC castlings */
16478               if(c >= 'a') { /* black rights */
16479                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16480                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16481                   if(i == BOARD_RGHT) break;
16482                   board[CASTLING][5] = i;
16483                   c -= AAA;
16484                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16485                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16486                   if(c > i)
16487                       board[CASTLING][3] = c;
16488                   else
16489                       board[CASTLING][4] = c;
16490               } else { /* white rights */
16491                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16492                     if(board[0][i] == WhiteKing) break;
16493                   if(i == BOARD_RGHT) break;
16494                   board[CASTLING][2] = i;
16495                   c -= AAA - 'a' + 'A';
16496                   if(board[0][c] >= WhiteKing) break;
16497                   if(c > i)
16498                       board[CASTLING][0] = c;
16499                   else
16500                       board[CASTLING][1] = c;
16501               }
16502         }
16503       }
16504       for(i=0; i<nrCastlingRights; i++)
16505         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16506     if (appData.debugMode) {
16507         fprintf(debugFP, "FEN castling rights:");
16508         for(i=0; i<nrCastlingRights; i++)
16509         fprintf(debugFP, " %d", board[CASTLING][i]);
16510         fprintf(debugFP, "\n");
16511     }
16512
16513       while(*p==' ') p++;
16514     }
16515
16516     /* read e.p. field in games that know e.p. capture */
16517     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16518        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16519       if(*p=='-') {
16520         p++; board[EP_STATUS] = EP_NONE;
16521       } else {
16522          char c = *p++ - AAA;
16523
16524          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16525          if(*p >= '0' && *p <='9') p++;
16526          board[EP_STATUS] = c;
16527       }
16528     }
16529
16530
16531     if(sscanf(p, "%d", &i) == 1) {
16532         FENrulePlies = i; /* 50-move ply counter */
16533         /* (The move number is still ignored)    */
16534     }
16535
16536     return TRUE;
16537 }
16538
16539 void
16540 EditPositionPasteFEN(char *fen)
16541 {
16542   if (fen != NULL) {
16543     Board initial_position;
16544
16545     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16546       DisplayError(_("Bad FEN position in clipboard"), 0);
16547       return ;
16548     } else {
16549       int savedBlackPlaysFirst = blackPlaysFirst;
16550       EditPositionEvent();
16551       blackPlaysFirst = savedBlackPlaysFirst;
16552       CopyBoard(boards[0], initial_position);
16553       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16554       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16555       DisplayBothClocks();
16556       DrawPosition(FALSE, boards[currentMove]);
16557     }
16558   }
16559 }
16560
16561 static char cseq[12] = "\\   ";
16562
16563 Boolean set_cont_sequence(char *new_seq)
16564 {
16565     int len;
16566     Boolean ret;
16567
16568     // handle bad attempts to set the sequence
16569         if (!new_seq)
16570                 return 0; // acceptable error - no debug
16571
16572     len = strlen(new_seq);
16573     ret = (len > 0) && (len < sizeof(cseq));
16574     if (ret)
16575       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16576     else if (appData.debugMode)
16577       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16578     return ret;
16579 }
16580
16581 /*
16582     reformat a source message so words don't cross the width boundary.  internal
16583     newlines are not removed.  returns the wrapped size (no null character unless
16584     included in source message).  If dest is NULL, only calculate the size required
16585     for the dest buffer.  lp argument indicats line position upon entry, and it's
16586     passed back upon exit.
16587 */
16588 int wrap(char *dest, char *src, int count, int width, int *lp)
16589 {
16590     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16591
16592     cseq_len = strlen(cseq);
16593     old_line = line = *lp;
16594     ansi = len = clen = 0;
16595
16596     for (i=0; i < count; i++)
16597     {
16598         if (src[i] == '\033')
16599             ansi = 1;
16600
16601         // if we hit the width, back up
16602         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16603         {
16604             // store i & len in case the word is too long
16605             old_i = i, old_len = len;
16606
16607             // find the end of the last word
16608             while (i && src[i] != ' ' && src[i] != '\n')
16609             {
16610                 i--;
16611                 len--;
16612             }
16613
16614             // word too long?  restore i & len before splitting it
16615             if ((old_i-i+clen) >= width)
16616             {
16617                 i = old_i;
16618                 len = old_len;
16619             }
16620
16621             // extra space?
16622             if (i && src[i-1] == ' ')
16623                 len--;
16624
16625             if (src[i] != ' ' && src[i] != '\n')
16626             {
16627                 i--;
16628                 if (len)
16629                     len--;
16630             }
16631
16632             // now append the newline and continuation sequence
16633             if (dest)
16634                 dest[len] = '\n';
16635             len++;
16636             if (dest)
16637                 strncpy(dest+len, cseq, cseq_len);
16638             len += cseq_len;
16639             line = cseq_len;
16640             clen = cseq_len;
16641             continue;
16642         }
16643
16644         if (dest)
16645             dest[len] = src[i];
16646         len++;
16647         if (!ansi)
16648             line++;
16649         if (src[i] == '\n')
16650             line = 0;
16651         if (src[i] == 'm')
16652             ansi = 0;
16653     }
16654     if (dest && appData.debugMode)
16655     {
16656         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16657             count, width, line, len, *lp);
16658         show_bytes(debugFP, src, count);
16659         fprintf(debugFP, "\ndest: ");
16660         show_bytes(debugFP, dest, len);
16661         fprintf(debugFP, "\n");
16662     }
16663     *lp = dest ? line : old_line;
16664
16665     return len;
16666 }
16667
16668 // [HGM] vari: routines for shelving variations
16669 Boolean modeRestore = FALSE;
16670
16671 void
16672 PushInner(int firstMove, int lastMove)
16673 {
16674         int i, j, nrMoves = lastMove - firstMove;
16675
16676         // push current tail of game on stack
16677         savedResult[storedGames] = gameInfo.result;
16678         savedDetails[storedGames] = gameInfo.resultDetails;
16679         gameInfo.resultDetails = NULL;
16680         savedFirst[storedGames] = firstMove;
16681         savedLast [storedGames] = lastMove;
16682         savedFramePtr[storedGames] = framePtr;
16683         framePtr -= nrMoves; // reserve space for the boards
16684         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16685             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16686             for(j=0; j<MOVE_LEN; j++)
16687                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16688             for(j=0; j<2*MOVE_LEN; j++)
16689                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16690             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16691             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16692             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16693             pvInfoList[firstMove+i-1].depth = 0;
16694             commentList[framePtr+i] = commentList[firstMove+i];
16695             commentList[firstMove+i] = NULL;
16696         }
16697
16698         storedGames++;
16699         forwardMostMove = firstMove; // truncate game so we can start variation
16700 }
16701
16702 void
16703 PushTail(int firstMove, int lastMove)
16704 {
16705         if(appData.icsActive) { // only in local mode
16706                 forwardMostMove = currentMove; // mimic old ICS behavior
16707                 return;
16708         }
16709         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16710
16711         PushInner(firstMove, lastMove);
16712         if(storedGames == 1) GreyRevert(FALSE);
16713         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16714 }
16715
16716 void
16717 PopInner(Boolean annotate)
16718 {
16719         int i, j, nrMoves;
16720         char buf[8000], moveBuf[20];
16721
16722         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16723         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16724         nrMoves = savedLast[storedGames] - currentMove;
16725         if(annotate) {
16726                 int cnt = 10;
16727                 if(!WhiteOnMove(currentMove))
16728                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16729                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16730                 for(i=currentMove; i<forwardMostMove; i++) {
16731                         if(WhiteOnMove(i))
16732                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16733                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16734                         strcat(buf, moveBuf);
16735                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16736                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16737                 }
16738                 strcat(buf, ")");
16739         }
16740         for(i=1; i<=nrMoves; i++) { // copy last variation back
16741             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16742             for(j=0; j<MOVE_LEN; j++)
16743                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16744             for(j=0; j<2*MOVE_LEN; j++)
16745                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16746             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16747             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16748             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16749             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16750             commentList[currentMove+i] = commentList[framePtr+i];
16751             commentList[framePtr+i] = NULL;
16752         }
16753         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16754         framePtr = savedFramePtr[storedGames];
16755         gameInfo.result = savedResult[storedGames];
16756         if(gameInfo.resultDetails != NULL) {
16757             free(gameInfo.resultDetails);
16758       }
16759         gameInfo.resultDetails = savedDetails[storedGames];
16760         forwardMostMove = currentMove + nrMoves;
16761 }
16762
16763 Boolean
16764 PopTail(Boolean annotate)
16765 {
16766         if(appData.icsActive) return FALSE; // only in local mode
16767         if(!storedGames) return FALSE; // sanity
16768         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16769
16770         PopInner(annotate);
16771         if(currentMove < forwardMostMove) ForwardEvent(); else
16772         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16773
16774         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16775         return TRUE;
16776 }
16777
16778 void
16779 CleanupTail()
16780 {       // remove all shelved variations
16781         int i;
16782         for(i=0; i<storedGames; i++) {
16783             if(savedDetails[i])
16784                 free(savedDetails[i]);
16785             savedDetails[i] = NULL;
16786         }
16787         for(i=framePtr; i<MAX_MOVES; i++) {
16788                 if(commentList[i]) free(commentList[i]);
16789                 commentList[i] = NULL;
16790         }
16791         framePtr = MAX_MOVES-1;
16792         storedGames = 0;
16793 }
16794
16795 void
16796 LoadVariation(int index, char *text)
16797 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16798         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16799         int level = 0, move;
16800
16801         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16802         // first find outermost bracketing variation
16803         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16804             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16805                 if(*p == '{') wait = '}'; else
16806                 if(*p == '[') wait = ']'; else
16807                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16808                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16809             }
16810             if(*p == wait) wait = NULLCHAR; // closing ]} found
16811             p++;
16812         }
16813         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16814         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16815         end[1] = NULLCHAR; // clip off comment beyond variation
16816         ToNrEvent(currentMove-1);
16817         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16818         // kludge: use ParsePV() to append variation to game
16819         move = currentMove;
16820         ParsePV(start, TRUE, TRUE);
16821         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16822         ClearPremoveHighlights();
16823         CommentPopDown();
16824         ToNrEvent(currentMove+1);
16825 }
16826