eec214e794b4c657fba37a23f5a7c1fd2c52efd6
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
236
237 #ifdef WIN32
238        extern void ConsoleCreate();
239 #endif
240
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
244
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
252 Boolean abortMatch;
253
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 int endPV = -1;
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
261 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
265 Boolean partnerUp;
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
277 int chattingPartner;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
283
284 /* States for ics_getting_history */
285 #define H_FALSE 0
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
291
292 /* whosays values for GameEnds */
293 #define GE_ICS 0
294 #define GE_ENGINE 1
295 #define GE_PLAYER 2
296 #define GE_FILE 3
297 #define GE_XBOARD 4
298 #define GE_ENGINE1 5
299 #define GE_ENGINE2 6
300
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
303
304 /* Different types of move when calling RegisterMove */
305 #define CMAIL_MOVE   0
306 #define CMAIL_RESIGN 1
307 #define CMAIL_DRAW   2
308 #define CMAIL_ACCEPT 3
309
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
314
315 /* Telnet protocol constants */
316 #define TN_WILL 0373
317 #define TN_WONT 0374
318 #define TN_DO   0375
319 #define TN_DONT 0376
320 #define TN_IAC  0377
321 #define TN_ECHO 0001
322 #define TN_SGA  0003
323 #define TN_PORT 23
324
325 char*
326 safeStrCpy( char *dst, const char *src, size_t count )
327 { // [HGM] made safe
328   int i;
329   assert( dst != NULL );
330   assert( src != NULL );
331   assert( count > 0 );
332
333   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334   if(  i == count && dst[count-1] != NULLCHAR)
335     {
336       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337       if(appData.debugMode)
338       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339     }
340
341   return dst;
342 }
343
344 /* Some compiler can't cast u64 to double
345  * This function do the job for us:
346
347  * We use the highest bit for cast, this only
348  * works if the highest bit is not
349  * in use (This should not happen)
350  *
351  * We used this for all compiler
352  */
353 double
354 u64ToDouble(u64 value)
355 {
356   double r;
357   u64 tmp = value & u64Const(0x7fffffffffffffff);
358   r = (double)(s64)tmp;
359   if (value & u64Const(0x8000000000000000))
360        r +=  9.2233720368547758080e18; /* 2^63 */
361  return r;
362 }
363
364 /* Fake up flags for now, as we aren't keeping track of castling
365    availability yet. [HGM] Change of logic: the flag now only
366    indicates the type of castlings allowed by the rule of the game.
367    The actual rights themselves are maintained in the array
368    castlingRights, as part of the game history, and are not probed
369    by this function.
370  */
371 int
372 PosFlags(index)
373 {
374   int flags = F_ALL_CASTLE_OK;
375   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376   switch (gameInfo.variant) {
377   case VariantSuicide:
378     flags &= ~F_ALL_CASTLE_OK;
379   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380     flags |= F_IGNORE_CHECK;
381   case VariantLosers:
382     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
383     break;
384   case VariantAtomic:
385     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386     break;
387   case VariantKriegspiel:
388     flags |= F_KRIEGSPIEL_CAPTURE;
389     break;
390   case VariantCapaRandom:
391   case VariantFischeRandom:
392     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393   case VariantNoCastle:
394   case VariantShatranj:
395   case VariantCourier:
396   case VariantMakruk:
397   case VariantGrand:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 Boolean shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 ChessSquare GrandArray[2][BOARD_FILES] = {
602     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
603         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
604     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
605         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
606 };
607
608 #ifdef GOTHIC
609 ChessSquare GothicArray[2][BOARD_FILES] = {
610     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
611         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
612     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
613         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 };
615 #else // !GOTHIC
616 #define GothicArray CapablancaArray
617 #endif // !GOTHIC
618
619 #ifdef FALCON
620 ChessSquare FalconArray[2][BOARD_FILES] = {
621     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
622         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
623     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
624         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
625 };
626 #else // !FALCON
627 #define FalconArray CapablancaArray
628 #endif // !FALCON
629
630 #else // !(BOARD_FILES>=10)
631 #define XiangqiPosition FIDEArray
632 #define CapablancaArray FIDEArray
633 #define GothicArray FIDEArray
634 #define GreatArray FIDEArray
635 #endif // !(BOARD_FILES>=10)
636
637 #if (BOARD_FILES>=12)
638 ChessSquare CourierArray[2][BOARD_FILES] = {
639     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
640         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
641     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
642         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
643 };
644 #else // !(BOARD_FILES>=12)
645 #define CourierArray CapablancaArray
646 #endif // !(BOARD_FILES>=12)
647
648
649 Board initialPosition;
650
651
652 /* Convert str to a rating. Checks for special cases of "----",
653
654    "++++", etc. Also strips ()'s */
655 int
656 string_to_rating(str)
657   char *str;
658 {
659   while(*str && !isdigit(*str)) ++str;
660   if (!*str)
661     return 0;   /* One of the special "no rating" cases */
662   else
663     return atoi(str);
664 }
665
666 void
667 ClearProgramStats()
668 {
669     /* Init programStats */
670     programStats.movelist[0] = 0;
671     programStats.depth = 0;
672     programStats.nr_moves = 0;
673     programStats.moves_left = 0;
674     programStats.nodes = 0;
675     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
676     programStats.score = 0;
677     programStats.got_only_move = 0;
678     programStats.got_fail = 0;
679     programStats.line_is_book = 0;
680 }
681
682 void
683 CommonEngineInit()
684 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
685     if (appData.firstPlaysBlack) {
686         first.twoMachinesColor = "black\n";
687         second.twoMachinesColor = "white\n";
688     } else {
689         first.twoMachinesColor = "white\n";
690         second.twoMachinesColor = "black\n";
691     }
692
693     first.other = &second;
694     second.other = &first;
695
696     { float norm = 1;
697         if(appData.timeOddsMode) {
698             norm = appData.timeOdds[0];
699             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
700         }
701         first.timeOdds  = appData.timeOdds[0]/norm;
702         second.timeOdds = appData.timeOdds[1]/norm;
703     }
704
705     if(programVersion) free(programVersion);
706     if (appData.noChessProgram) {
707         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
708         sprintf(programVersion, "%s", PACKAGE_STRING);
709     } else {
710       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
711       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
712       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
713     }
714 }
715
716 void
717 UnloadEngine(ChessProgramState *cps)
718 {
719         /* Kill off first chess program */
720         if (cps->isr != NULL)
721           RemoveInputSource(cps->isr);
722         cps->isr = NULL;
723
724         if (cps->pr != NoProc) {
725             ExitAnalyzeMode();
726             DoSleep( appData.delayBeforeQuit );
727             SendToProgram("quit\n", cps);
728             DoSleep( appData.delayAfterQuit );
729             DestroyChildProcess(cps->pr, cps->useSigterm);
730         }
731         cps->pr = NoProc;
732         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
733 }
734
735 void
736 ClearOptions(ChessProgramState *cps)
737 {
738     int i;
739     cps->nrOptions = cps->comboCnt = 0;
740     for(i=0; i<MAX_OPTIONS; i++) {
741         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
742         cps->option[i].textValue = 0;
743     }
744 }
745
746 char *engineNames[] = {
747 "first",
748 "second"
749 };
750
751 void
752 InitEngine(ChessProgramState *cps, int n)
753 {   // [HGM] all engine initialiation put in a function that does one engine
754
755     ClearOptions(cps);
756
757     cps->which = engineNames[n];
758     cps->maybeThinking = FALSE;
759     cps->pr = NoProc;
760     cps->isr = NULL;
761     cps->sendTime = 2;
762     cps->sendDrawOffers = 1;
763
764     cps->program = appData.chessProgram[n];
765     cps->host = appData.host[n];
766     cps->dir = appData.directory[n];
767     cps->initString = appData.engInitString[n];
768     cps->computerString = appData.computerString[n];
769     cps->useSigint  = TRUE;
770     cps->useSigterm = TRUE;
771     cps->reuse = appData.reuse[n];
772     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
773     cps->useSetboard = FALSE;
774     cps->useSAN = FALSE;
775     cps->usePing = FALSE;
776     cps->lastPing = 0;
777     cps->lastPong = 0;
778     cps->usePlayother = FALSE;
779     cps->useColors = TRUE;
780     cps->useUsermove = FALSE;
781     cps->sendICS = FALSE;
782     cps->sendName = appData.icsActive;
783     cps->sdKludge = FALSE;
784     cps->stKludge = FALSE;
785     TidyProgramName(cps->program, cps->host, cps->tidy);
786     cps->matchWins = 0;
787     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
788     cps->analysisSupport = 2; /* detect */
789     cps->analyzing = FALSE;
790     cps->initDone = FALSE;
791
792     /* New features added by Tord: */
793     cps->useFEN960 = FALSE;
794     cps->useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     cps->fenOverride  = appData.fenOverride[n];
797
798     /* [HGM] time odds: set factor for each machine */
799     cps->timeOdds  = appData.timeOdds[n];
800
801     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
802     cps->accumulateTC = appData.accumulateTC[n];
803     cps->maxNrOfSessions = 1;
804
805     /* [HGM] debug */
806     cps->debug = FALSE;
807
808     cps->supportsNPS = UNKNOWN;
809     cps->memSize = FALSE;
810     cps->maxCores = FALSE;
811     cps->egtFormats[0] = NULLCHAR;
812
813     /* [HGM] options */
814     cps->optionSettings  = appData.engOptions[n];
815
816     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
817     cps->isUCI = appData.isUCI[n]; /* [AS] */
818     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
819
820     if (appData.protocolVersion[n] > PROTOVER
821         || appData.protocolVersion[n] < 1)
822       {
823         char buf[MSG_SIZ];
824         int len;
825
826         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
827                        appData.protocolVersion[n]);
828         if( (len > MSG_SIZ) && appData.debugMode )
829           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
830
831         DisplayFatalError(buf, 0, 2);
832       }
833     else
834       {
835         cps->protocolVersion = appData.protocolVersion[n];
836       }
837
838     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
839 }
840
841 ChessProgramState *savCps;
842
843 void
844 LoadEngine()
845 {
846     int i;
847     if(WaitForEngine(savCps, LoadEngine)) return;
848     CommonEngineInit(); // recalculate time odds
849     if(gameInfo.variant != StringToVariant(appData.variant)) {
850         // we changed variant when loading the engine; this forces us to reset
851         Reset(TRUE, savCps != &first);
852         EditGameEvent(); // for consistency with other path, as Reset changes mode
853     }
854     InitChessProgram(savCps, FALSE);
855     SendToProgram("force\n", savCps);
856     DisplayMessage("", "");
857     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
858     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859     ThawUI();
860     SetGNUMode();
861 }
862
863 void
864 ReplaceEngine(ChessProgramState *cps, int n)
865 {
866     EditGameEvent();
867     UnloadEngine(cps);
868     appData.noChessProgram = FALSE;
869     appData.clockMode = TRUE;
870     InitEngine(cps, n);
871     UpdateLogos(TRUE);
872     if(n) return; // only startup first engine immediately; second can wait
873     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874     LoadEngine();
875 }
876
877 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
878 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
879
880 static char resetOptions[] = 
881         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
882         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
883         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
884
885 void
886 Load(ChessProgramState *cps, int i)
887 {
888     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
889     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
890         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
891         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
892         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
893         ParseArgsFromString(buf);
894         SwapEngines(i);
895         ReplaceEngine(cps, i);
896         return;
897     }
898     p = engineName;
899     while(q = strchr(p, SLASH)) p = q+1;
900     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
901     if(engineDir[0] != NULLCHAR)
902         appData.directory[i] = engineDir;
903     else if(p != engineName) { // derive directory from engine path, when not given
904         p[-1] = 0;
905         appData.directory[i] = strdup(engineName);
906         p[-1] = SLASH;
907     } else appData.directory[i] = ".";
908     if(params[0]) {
909         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
910         snprintf(command, MSG_SIZ, "%s %s", p, params);
911         p = command;
912     }
913     appData.chessProgram[i] = strdup(p);
914     appData.isUCI[i] = isUCI;
915     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
916     appData.hasOwnBookUCI[i] = hasBook;
917     if(!nickName[0]) useNick = FALSE;
918     if(useNick) ASSIGN(appData.pgnName[i], nickName);
919     if(addToList) {
920         int len;
921         char quote;
922         q = firstChessProgramNames;
923         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
924         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
926                         quote, p, quote, appData.directory[i], 
927                         useNick ? " -fn \"" : "",
928                         useNick ? nickName : "",
929                         useNick ? "\"" : "",
930                         v1 ? " -firstProtocolVersion 1" : "",
931                         hasBook ? "" : " -fNoOwnBookUCI",
932                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
933                         storeVariant ? " -variant " : "",
934                         storeVariant ? VariantName(gameInfo.variant) : "");
935         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
936         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
937         if(q)   free(q);
938     }
939     ReplaceEngine(cps, i);
940 }
941
942 void
943 InitTimeControls()
944 {
945     int matched, min, sec;
946     /*
947      * Parse timeControl resource
948      */
949     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
950                           appData.movesPerSession)) {
951         char buf[MSG_SIZ];
952         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
953         DisplayFatalError(buf, 0, 2);
954     }
955
956     /*
957      * Parse searchTime resource
958      */
959     if (*appData.searchTime != NULLCHAR) {
960         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
961         if (matched == 1) {
962             searchTime = min * 60;
963         } else if (matched == 2) {
964             searchTime = min * 60 + sec;
965         } else {
966             char buf[MSG_SIZ];
967             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
968             DisplayFatalError(buf, 0, 2);
969         }
970     }
971 }
972
973 void
974 InitBackEnd1()
975 {
976
977     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
978     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
979
980     GetTimeMark(&programStartTime);
981     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
982     appData.seedBase = random() + (random()<<15);
983     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984
985     ClearProgramStats();
986     programStats.ok_to_send = 1;
987     programStats.seen_stat = 0;
988
989     /*
990      * Initialize game list
991      */
992     ListNew(&gameList);
993
994
995     /*
996      * Internet chess server status
997      */
998     if (appData.icsActive) {
999         appData.matchMode = FALSE;
1000         appData.matchGames = 0;
1001 #if ZIPPY
1002         appData.noChessProgram = !appData.zippyPlay;
1003 #else
1004         appData.zippyPlay = FALSE;
1005         appData.zippyTalk = FALSE;
1006         appData.noChessProgram = TRUE;
1007 #endif
1008         if (*appData.icsHelper != NULLCHAR) {
1009             appData.useTelnet = TRUE;
1010             appData.telnetProgram = appData.icsHelper;
1011         }
1012     } else {
1013         appData.zippyTalk = appData.zippyPlay = FALSE;
1014     }
1015
1016     /* [AS] Initialize pv info list [HGM] and game state */
1017     {
1018         int i, j;
1019
1020         for( i=0; i<=framePtr; i++ ) {
1021             pvInfoList[i].depth = -1;
1022             boards[i][EP_STATUS] = EP_NONE;
1023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1024         }
1025     }
1026
1027     InitTimeControls();
1028
1029     /* [AS] Adjudication threshold */
1030     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031
1032     InitEngine(&first, 0);
1033     InitEngine(&second, 1);
1034     CommonEngineInit();
1035
1036     pairing.which = "pairing"; // pairing engine
1037     pairing.pr = NoProc;
1038     pairing.isr = NULL;
1039     pairing.program = appData.pairingEngine;
1040     pairing.host = "localhost";
1041     pairing.dir = ".";
1042
1043     if (appData.icsActive) {
1044         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1045     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046         appData.clockMode = FALSE;
1047         first.sendTime = second.sendTime = 0;
1048     }
1049
1050 #if ZIPPY
1051     /* Override some settings from environment variables, for backward
1052        compatibility.  Unfortunately it's not feasible to have the env
1053        vars just set defaults, at least in xboard.  Ugh.
1054     */
1055     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1056       ZippyInit();
1057     }
1058 #endif
1059
1060     if (!appData.icsActive) {
1061       char buf[MSG_SIZ];
1062       int len;
1063
1064       /* Check for variants that are supported only in ICS mode,
1065          or not at all.  Some that are accepted here nevertheless
1066          have bugs; see comments below.
1067       */
1068       VariantClass variant = StringToVariant(appData.variant);
1069       switch (variant) {
1070       case VariantBughouse:     /* need four players and two boards */
1071       case VariantKriegspiel:   /* need to hide pieces and move details */
1072         /* case VariantFischeRandom: (Fabien: moved below) */
1073         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074         if( (len > MSG_SIZ) && appData.debugMode )
1075           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076
1077         DisplayFatalError(buf, 0, 2);
1078         return;
1079
1080       case VariantUnknown:
1081       case VariantLoadable:
1082       case Variant29:
1083       case Variant30:
1084       case Variant31:
1085       case Variant32:
1086       case Variant33:
1087       case Variant34:
1088       case Variant35:
1089       case Variant36:
1090       default:
1091         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092         if( (len > MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1099       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1100       case VariantGothic:     /* [HGM] should work */
1101       case VariantCapablanca: /* [HGM] should work */
1102       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1103       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1104       case VariantKnightmate: /* [HGM] should work */
1105       case VariantCylinder:   /* [HGM] untested */
1106       case VariantFalcon:     /* [HGM] untested */
1107       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108                                  offboard interposition not understood */
1109       case VariantNormal:     /* definitely works! */
1110       case VariantWildCastle: /* pieces not automatically shuffled */
1111       case VariantNoCastle:   /* pieces not automatically shuffled */
1112       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113       case VariantLosers:     /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantSuicide:    /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantGiveaway:   /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantTwoKings:   /* should work */
1120       case VariantAtomic:     /* should work except for win condition */
1121       case Variant3Check:     /* should work except for win condition */
1122       case VariantShatranj:   /* should work except for all win conditions */
1123       case VariantMakruk:     /* should work except for draw countdown */
1124       case VariantBerolina:   /* might work if TestLegality is off */
1125       case VariantCapaRandom: /* should work */
1126       case VariantJanus:      /* should work */
1127       case VariantSuper:      /* experimental */
1128       case VariantGreat:      /* experimental, requires legality testing to be off */
1129       case VariantSChess:     /* S-Chess, should work */
1130       case VariantGrand:      /* should work */
1131       case VariantSpartan:    /* should work */
1132         break;
1133       }
1134     }
1135
1136 }
1137
1138 int NextIntegerFromString( char ** str, long * value )
1139 {
1140     int result = -1;
1141     char * s = *str;
1142
1143     while( *s == ' ' || *s == '\t' ) {
1144         s++;
1145     }
1146
1147     *value = 0;
1148
1149     if( *s >= '0' && *s <= '9' ) {
1150         while( *s >= '0' && *s <= '9' ) {
1151             *value = *value * 10 + (*s - '0');
1152             s++;
1153         }
1154
1155         result = 0;
1156     }
1157
1158     *str = s;
1159
1160     return result;
1161 }
1162
1163 int NextTimeControlFromString( char ** str, long * value )
1164 {
1165     long temp;
1166     int result = NextIntegerFromString( str, &temp );
1167
1168     if( result == 0 ) {
1169         *value = temp * 60; /* Minutes */
1170         if( **str == ':' ) {
1171             (*str)++;
1172             result = NextIntegerFromString( str, &temp );
1173             *value += temp; /* Seconds */
1174         }
1175     }
1176
1177     return result;
1178 }
1179
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182     int result = -1, type = 0; long temp, temp2;
1183
1184     if(**str != ':') return -1; // old params remain in force!
1185     (*str)++;
1186     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187     if( NextIntegerFromString( str, &temp ) ) return -1;
1188     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189
1190     if(**str != '/') {
1191         /* time only: incremental or sudden-death time control */
1192         if(**str == '+') { /* increment follows; read it */
1193             (*str)++;
1194             if(**str == '!') type = *(*str)++; // Bronstein TC
1195             if(result = NextIntegerFromString( str, &temp2)) return -1;
1196             *inc = temp2 * 1000;
1197             if(**str == '.') { // read fraction of increment
1198                 char *start = ++(*str);
1199                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200                 temp2 *= 1000;
1201                 while(start++ < *str) temp2 /= 10;
1202                 *inc += temp2;
1203             }
1204         } else *inc = 0;
1205         *moves = 0; *tc = temp * 1000; *incType = type;
1206         return 0;
1207     }
1208
1209     (*str)++; /* classical time control */
1210     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211
1212     if(result == 0) {
1213         *moves = temp;
1214         *tc    = temp2 * 1000;
1215         *inc   = 0;
1216         *incType = type;
1217     }
1218     return result;
1219 }
1220
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 {   /* [HGM] get time to add from the multi-session time-control string */
1223     int incType, moves=1; /* kludge to force reading of first session */
1224     long time, increment;
1225     char *s = tcString;
1226
1227     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229     do {
1230         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233         if(movenr == -1) return time;    /* last move before new session     */
1234         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236         if(!moves) return increment;     /* current session is incremental   */
1237         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238     } while(movenr >= -1);               /* try again for next session       */
1239
1240     return 0; // no new time quota on this move
1241 }
1242
1243 int
1244 ParseTimeControl(tc, ti, mps)
1245      char *tc;
1246      float ti;
1247      int mps;
1248 {
1249   long tc1;
1250   long tc2;
1251   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252   int min, sec=0;
1253
1254   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1257   if(ti > 0) {
1258
1259     if(mps)
1260       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261     else 
1262       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263   } else {
1264     if(mps)
1265       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266     else 
1267       snprintf(buf, MSG_SIZ, ":%s", mytc);
1268   }
1269   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270   
1271   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1272     return FALSE;
1273   }
1274
1275   if( *tc == '/' ) {
1276     /* Parse second time control */
1277     tc++;
1278
1279     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1280       return FALSE;
1281     }
1282
1283     if( tc2 == 0 ) {
1284       return FALSE;
1285     }
1286
1287     timeControl_2 = tc2 * 1000;
1288   }
1289   else {
1290     timeControl_2 = 0;
1291   }
1292
1293   if( tc1 == 0 ) {
1294     return FALSE;
1295   }
1296
1297   timeControl = tc1 * 1000;
1298
1299   if (ti >= 0) {
1300     timeIncrement = ti * 1000;  /* convert to ms */
1301     movesPerSession = 0;
1302   } else {
1303     timeIncrement = 0;
1304     movesPerSession = mps;
1305   }
1306   return TRUE;
1307 }
1308
1309 void
1310 InitBackEnd2()
1311 {
1312     if (appData.debugMode) {
1313         fprintf(debugFP, "%s\n", programVersion);
1314     }
1315
1316     set_cont_sequence(appData.wrapContSeq);
1317     if (appData.matchGames > 0) {
1318         appData.matchMode = TRUE;
1319     } else if (appData.matchMode) {
1320         appData.matchGames = 1;
1321     }
1322     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323         appData.matchGames = appData.sameColorGames;
1324     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327     }
1328     Reset(TRUE, FALSE);
1329     if (appData.noChessProgram || first.protocolVersion == 1) {
1330       InitBackEnd3();
1331     } else {
1332       /* kludge: allow timeout for initial "feature" commands */
1333       FreezeUI();
1334       DisplayMessage("", _("Starting chess program"));
1335       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1336     }
1337 }
1338
1339 int
1340 CalculateIndex(int index, int gameNr)
1341 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342     int res;
1343     if(index > 0) return index; // fixed nmber
1344     if(index == 0) return 1;
1345     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1347     return res;
1348 }
1349
1350 int
1351 LoadGameOrPosition(int gameNr)
1352 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353     if (*appData.loadGameFile != NULLCHAR) {
1354         if (!LoadGameFromFile(appData.loadGameFile,
1355                 CalculateIndex(appData.loadGameIndex, gameNr),
1356                               appData.loadGameFile, FALSE)) {
1357             DisplayFatalError(_("Bad game file"), 0, 1);
1358             return 0;
1359         }
1360     } else if (*appData.loadPositionFile != NULLCHAR) {
1361         if (!LoadPositionFromFile(appData.loadPositionFile,
1362                 CalculateIndex(appData.loadPositionIndex, gameNr),
1363                                   appData.loadPositionFile)) {
1364             DisplayFatalError(_("Bad position file"), 0, 1);
1365             return 0;
1366         }
1367     }
1368     return 1;
1369 }
1370
1371 void
1372 ReserveGame(int gameNr, char resChar)
1373 {
1374     FILE *tf = fopen(appData.tourneyFile, "r+");
1375     char *p, *q, c, buf[MSG_SIZ];
1376     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377     safeStrCpy(buf, lastMsg, MSG_SIZ);
1378     DisplayMessage(_("Pick new game"), "");
1379     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380     ParseArgsFromFile(tf);
1381     p = q = appData.results;
1382     if(appData.debugMode) {
1383       char *r = appData.participants;
1384       fprintf(debugFP, "results = '%s'\n", p);
1385       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386       fprintf(debugFP, "\n");
1387     }
1388     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389     nextGame = q - p;
1390     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391     safeStrCpy(q, p, strlen(p) + 2);
1392     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396         q[nextGame] = '*';
1397     }
1398     fseek(tf, -(strlen(p)+4), SEEK_END);
1399     c = fgetc(tf);
1400     if(c != '"') // depending on DOS or Unix line endings we can be one off
1401          fseek(tf, -(strlen(p)+2), SEEK_END);
1402     else fseek(tf, -(strlen(p)+3), SEEK_END);
1403     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404     DisplayMessage(buf, "");
1405     free(p); appData.results = q;
1406     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408         UnloadEngine(&first);  // next game belongs to other pairing;
1409         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1410     }
1411 }
1412
1413 void
1414 MatchEvent(int mode)
1415 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416         int dummy;
1417         if(matchMode) { // already in match mode: switch it off
1418             abortMatch = TRUE;
1419             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420             return;
1421         }
1422 //      if(gameMode != BeginningOfGame) {
1423 //          DisplayError(_("You can only start a match from the initial position."), 0);
1424 //          return;
1425 //      }
1426         abortMatch = FALSE;
1427         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428         /* Set up machine vs. machine match */
1429         nextGame = 0;
1430         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431         if(appData.tourneyFile[0]) {
1432             ReserveGame(-1, 0);
1433             if(nextGame > appData.matchGames) {
1434                 char buf[MSG_SIZ];
1435                 if(strchr(appData.results, '*') == NULL) {
1436                     FILE *f;
1437                     appData.tourneyCycles++;
1438                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1439                         fclose(f);
1440                         NextTourneyGame(-1, &dummy);
1441                         ReserveGame(-1, 0);
1442                         if(nextGame <= appData.matchGames) {
1443                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444                             matchMode = mode;
1445                             ScheduleDelayedEvent(NextMatchGame, 10000);
1446                             return;
1447                         }
1448                     }
1449                 }
1450                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451                 DisplayError(buf, 0);
1452                 appData.tourneyFile[0] = 0;
1453                 return;
1454             }
1455         } else
1456         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1457             DisplayFatalError(_("Can't have a match with no chess programs"),
1458                               0, 2);
1459             return;
1460         }
1461         matchMode = mode;
1462         matchGame = roundNr = 1;
1463         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1464         NextMatchGame();
1465 }
1466
1467 void
1468 InitBackEnd3 P((void))
1469 {
1470     GameMode initialMode;
1471     char buf[MSG_SIZ];
1472     int err, len;
1473
1474     InitChessProgram(&first, startedFromSetupPosition);
1475
1476     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1477         free(programVersion);
1478         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480     }
1481
1482     if (appData.icsActive) {
1483 #ifdef WIN32
1484         /* [DM] Make a console window if needed [HGM] merged ifs */
1485         ConsoleCreate();
1486 #endif
1487         err = establish();
1488         if (err != 0)
1489           {
1490             if (*appData.icsCommPort != NULLCHAR)
1491               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492                              appData.icsCommPort);
1493             else
1494               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495                         appData.icsHost, appData.icsPort);
1496
1497             if( (len > MSG_SIZ) && appData.debugMode )
1498               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499
1500             DisplayFatalError(buf, err, 1);
1501             return;
1502         }
1503         SetICSMode();
1504         telnetISR =
1505           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506         fromUserISR =
1507           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510     } else if (appData.noChessProgram) {
1511         SetNCPMode();
1512     } else {
1513         SetGNUMode();
1514     }
1515
1516     if (*appData.cmailGameName != NULLCHAR) {
1517         SetCmailMode();
1518         OpenLoopback(&cmailPR);
1519         cmailISR =
1520           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1521     }
1522
1523     ThawUI();
1524     DisplayMessage("", "");
1525     if (StrCaseCmp(appData.initialMode, "") == 0) {
1526       initialMode = BeginningOfGame;
1527       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531         ModeHighlight();
1532       }
1533     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534       initialMode = TwoMachinesPlay;
1535     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536       initialMode = AnalyzeFile;
1537     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538       initialMode = AnalyzeMode;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540       initialMode = MachinePlaysWhite;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542       initialMode = MachinePlaysBlack;
1543     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544       initialMode = EditGame;
1545     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546       initialMode = EditPosition;
1547     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548       initialMode = Training;
1549     } else {
1550       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551       if( (len > MSG_SIZ) && appData.debugMode )
1552         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553
1554       DisplayFatalError(buf, 0, 2);
1555       return;
1556     }
1557
1558     if (appData.matchMode) {
1559         if(appData.tourneyFile[0]) { // start tourney from command line
1560             FILE *f;
1561             if(f = fopen(appData.tourneyFile, "r")) {
1562                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563                 fclose(f);
1564                 appData.clockMode = TRUE;
1565                 SetGNUMode();
1566             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567         }
1568         MatchEvent(TRUE);
1569     } else if (*appData.cmailGameName != NULLCHAR) {
1570         /* Set up cmail mode */
1571         ReloadCmailMsgEvent(TRUE);
1572     } else {
1573         /* Set up other modes */
1574         if (initialMode == AnalyzeFile) {
1575           if (*appData.loadGameFile == NULLCHAR) {
1576             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1577             return;
1578           }
1579         }
1580         if (*appData.loadGameFile != NULLCHAR) {
1581             (void) LoadGameFromFile(appData.loadGameFile,
1582                                     appData.loadGameIndex,
1583                                     appData.loadGameFile, TRUE);
1584         } else if (*appData.loadPositionFile != NULLCHAR) {
1585             (void) LoadPositionFromFile(appData.loadPositionFile,
1586                                         appData.loadPositionIndex,
1587                                         appData.loadPositionFile);
1588             /* [HGM] try to make self-starting even after FEN load */
1589             /* to allow automatic setup of fairy variants with wtm */
1590             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591                 gameMode = BeginningOfGame;
1592                 setboardSpoiledMachineBlack = 1;
1593             }
1594             /* [HGM] loadPos: make that every new game uses the setup */
1595             /* from file as long as we do not switch variant          */
1596             if(!blackPlaysFirst) {
1597                 startedFromPositionFile = TRUE;
1598                 CopyBoard(filePosition, boards[0]);
1599             }
1600         }
1601         if (initialMode == AnalyzeMode) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1608             return;
1609           }
1610           AnalyzeModeEvent();
1611         } else if (initialMode == AnalyzeFile) {
1612           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613           ShowThinkingEvent();
1614           AnalyzeFileEvent();
1615           AnalysisPeriodicEvent(1);
1616         } else if (initialMode == MachinePlaysWhite) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineWhiteEvent();
1628         } else if (initialMode == MachinePlaysBlack) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           MachineBlackEvent();
1640         } else if (initialMode == TwoMachinesPlay) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           TwoMachinesEvent();
1652         } else if (initialMode == EditGame) {
1653           EditGameEvent();
1654         } else if (initialMode == EditPosition) {
1655           EditPositionEvent();
1656         } else if (initialMode == Training) {
1657           if (*appData.loadGameFile == NULLCHAR) {
1658             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659             return;
1660           }
1661           TrainingEvent();
1662         }
1663     }
1664 }
1665
1666 void
1667 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1668 {
1669     DisplayBook(current+1);
1670
1671     MoveHistorySet( movelist, first, last, current, pvInfoList );
1672
1673     EvalGraphSet( first, last, current, pvInfoList );
1674
1675     MakeEngineOutputTitle();
1676 }
1677
1678 /*
1679  * Establish will establish a contact to a remote host.port.
1680  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1681  *  used to talk to the host.
1682  * Returns 0 if okay, error code if not.
1683  */
1684 int
1685 establish()
1686 {
1687     char buf[MSG_SIZ];
1688
1689     if (*appData.icsCommPort != NULLCHAR) {
1690         /* Talk to the host through a serial comm port */
1691         return OpenCommPort(appData.icsCommPort, &icsPR);
1692
1693     } else if (*appData.gateway != NULLCHAR) {
1694         if (*appData.remoteShell == NULLCHAR) {
1695             /* Use the rcmd protocol to run telnet program on a gateway host */
1696             snprintf(buf, sizeof(buf), "%s %s %s",
1697                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1698             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1699
1700         } else {
1701             /* Use the rsh program to run telnet program on a gateway host */
1702             if (*appData.remoteUser == NULLCHAR) {
1703                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1704                         appData.gateway, appData.telnetProgram,
1705                         appData.icsHost, appData.icsPort);
1706             } else {
1707                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1708                         appData.remoteShell, appData.gateway,
1709                         appData.remoteUser, appData.telnetProgram,
1710                         appData.icsHost, appData.icsPort);
1711             }
1712             return StartChildProcess(buf, "", &icsPR);
1713
1714         }
1715     } else if (appData.useTelnet) {
1716         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1717
1718     } else {
1719         /* TCP socket interface differs somewhat between
1720            Unix and NT; handle details in the front end.
1721            */
1722         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1723     }
1724 }
1725
1726 void EscapeExpand(char *p, char *q)
1727 {       // [HGM] initstring: routine to shape up string arguments
1728         while(*p++ = *q++) if(p[-1] == '\\')
1729             switch(*q++) {
1730                 case 'n': p[-1] = '\n'; break;
1731                 case 'r': p[-1] = '\r'; break;
1732                 case 't': p[-1] = '\t'; break;
1733                 case '\\': p[-1] = '\\'; break;
1734                 case 0: *p = 0; return;
1735                 default: p[-1] = q[-1]; break;
1736             }
1737 }
1738
1739 void
1740 show_bytes(fp, buf, count)
1741      FILE *fp;
1742      char *buf;
1743      int count;
1744 {
1745     while (count--) {
1746         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1747             fprintf(fp, "\\%03o", *buf & 0xff);
1748         } else {
1749             putc(*buf, fp);
1750         }
1751         buf++;
1752     }
1753     fflush(fp);
1754 }
1755
1756 /* Returns an errno value */
1757 int
1758 OutputMaybeTelnet(pr, message, count, outError)
1759      ProcRef pr;
1760      char *message;
1761      int count;
1762      int *outError;
1763 {
1764     char buf[8192], *p, *q, *buflim;
1765     int left, newcount, outcount;
1766
1767     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1768         *appData.gateway != NULLCHAR) {
1769         if (appData.debugMode) {
1770             fprintf(debugFP, ">ICS: ");
1771             show_bytes(debugFP, message, count);
1772             fprintf(debugFP, "\n");
1773         }
1774         return OutputToProcess(pr, message, count, outError);
1775     }
1776
1777     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1778     p = message;
1779     q = buf;
1780     left = count;
1781     newcount = 0;
1782     while (left) {
1783         if (q >= buflim) {
1784             if (appData.debugMode) {
1785                 fprintf(debugFP, ">ICS: ");
1786                 show_bytes(debugFP, buf, newcount);
1787                 fprintf(debugFP, "\n");
1788             }
1789             outcount = OutputToProcess(pr, buf, newcount, outError);
1790             if (outcount < newcount) return -1; /* to be sure */
1791             q = buf;
1792             newcount = 0;
1793         }
1794         if (*p == '\n') {
1795             *q++ = '\r';
1796             newcount++;
1797         } else if (((unsigned char) *p) == TN_IAC) {
1798             *q++ = (char) TN_IAC;
1799             newcount ++;
1800         }
1801         *q++ = *p++;
1802         newcount++;
1803         left--;
1804     }
1805     if (appData.debugMode) {
1806         fprintf(debugFP, ">ICS: ");
1807         show_bytes(debugFP, buf, newcount);
1808         fprintf(debugFP, "\n");
1809     }
1810     outcount = OutputToProcess(pr, buf, newcount, outError);
1811     if (outcount < newcount) return -1; /* to be sure */
1812     return count;
1813 }
1814
1815 void
1816 read_from_player(isr, closure, message, count, error)
1817      InputSourceRef isr;
1818      VOIDSTAR closure;
1819      char *message;
1820      int count;
1821      int error;
1822 {
1823     int outError, outCount;
1824     static int gotEof = 0;
1825
1826     /* Pass data read from player on to ICS */
1827     if (count > 0) {
1828         gotEof = 0;
1829         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1830         if (outCount < count) {
1831             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1832         }
1833     } else if (count < 0) {
1834         RemoveInputSource(isr);
1835         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1836     } else if (gotEof++ > 0) {
1837         RemoveInputSource(isr);
1838         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1839     }
1840 }
1841
1842 void
1843 KeepAlive()
1844 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1845     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1846     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1847     SendToICS("date\n");
1848     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1849 }
1850
1851 /* added routine for printf style output to ics */
1852 void ics_printf(char *format, ...)
1853 {
1854     char buffer[MSG_SIZ];
1855     va_list args;
1856
1857     va_start(args, format);
1858     vsnprintf(buffer, sizeof(buffer), format, args);
1859     buffer[sizeof(buffer)-1] = '\0';
1860     SendToICS(buffer);
1861     va_end(args);
1862 }
1863
1864 void
1865 SendToICS(s)
1866      char *s;
1867 {
1868     int count, outCount, outError;
1869
1870     if (icsPR == NULL) return;
1871
1872     count = strlen(s);
1873     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1874     if (outCount < count) {
1875         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1876     }
1877 }
1878
1879 /* This is used for sending logon scripts to the ICS. Sending
1880    without a delay causes problems when using timestamp on ICC
1881    (at least on my machine). */
1882 void
1883 SendToICSDelayed(s,msdelay)
1884      char *s;
1885      long msdelay;
1886 {
1887     int count, outCount, outError;
1888
1889     if (icsPR == NULL) return;
1890
1891     count = strlen(s);
1892     if (appData.debugMode) {
1893         fprintf(debugFP, ">ICS: ");
1894         show_bytes(debugFP, s, count);
1895         fprintf(debugFP, "\n");
1896     }
1897     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1898                                       msdelay);
1899     if (outCount < count) {
1900         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901     }
1902 }
1903
1904
1905 /* Remove all highlighting escape sequences in s
1906    Also deletes any suffix starting with '('
1907    */
1908 char *
1909 StripHighlightAndTitle(s)
1910      char *s;
1911 {
1912     static char retbuf[MSG_SIZ];
1913     char *p = retbuf;
1914
1915     while (*s != NULLCHAR) {
1916         while (*s == '\033') {
1917             while (*s != NULLCHAR && !isalpha(*s)) s++;
1918             if (*s != NULLCHAR) s++;
1919         }
1920         while (*s != NULLCHAR && *s != '\033') {
1921             if (*s == '(' || *s == '[') {
1922                 *p = NULLCHAR;
1923                 return retbuf;
1924             }
1925             *p++ = *s++;
1926         }
1927     }
1928     *p = NULLCHAR;
1929     return retbuf;
1930 }
1931
1932 /* Remove all highlighting escape sequences in s */
1933 char *
1934 StripHighlight(s)
1935      char *s;
1936 {
1937     static char retbuf[MSG_SIZ];
1938     char *p = retbuf;
1939
1940     while (*s != NULLCHAR) {
1941         while (*s == '\033') {
1942             while (*s != NULLCHAR && !isalpha(*s)) s++;
1943             if (*s != NULLCHAR) s++;
1944         }
1945         while (*s != NULLCHAR && *s != '\033') {
1946             *p++ = *s++;
1947         }
1948     }
1949     *p = NULLCHAR;
1950     return retbuf;
1951 }
1952
1953 char *variantNames[] = VARIANT_NAMES;
1954 char *
1955 VariantName(v)
1956      VariantClass v;
1957 {
1958     return variantNames[v];
1959 }
1960
1961
1962 /* Identify a variant from the strings the chess servers use or the
1963    PGN Variant tag names we use. */
1964 VariantClass
1965 StringToVariant(e)
1966      char *e;
1967 {
1968     char *p;
1969     int wnum = -1;
1970     VariantClass v = VariantNormal;
1971     int i, found = FALSE;
1972     char buf[MSG_SIZ];
1973     int len;
1974
1975     if (!e) return v;
1976
1977     /* [HGM] skip over optional board-size prefixes */
1978     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1979         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1980         while( *e++ != '_');
1981     }
1982
1983     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1984         v = VariantNormal;
1985         found = TRUE;
1986     } else
1987     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1988       if (StrCaseStr(e, variantNames[i])) {
1989         v = (VariantClass) i;
1990         found = TRUE;
1991         break;
1992       }
1993     }
1994
1995     if (!found) {
1996       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1997           || StrCaseStr(e, "wild/fr")
1998           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1999         v = VariantFischeRandom;
2000       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2001                  (i = 1, p = StrCaseStr(e, "w"))) {
2002         p += i;
2003         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2004         if (isdigit(*p)) {
2005           wnum = atoi(p);
2006         } else {
2007           wnum = -1;
2008         }
2009         switch (wnum) {
2010         case 0: /* FICS only, actually */
2011         case 1:
2012           /* Castling legal even if K starts on d-file */
2013           v = VariantWildCastle;
2014           break;
2015         case 2:
2016         case 3:
2017         case 4:
2018           /* Castling illegal even if K & R happen to start in
2019              normal positions. */
2020           v = VariantNoCastle;
2021           break;
2022         case 5:
2023         case 7:
2024         case 8:
2025         case 10:
2026         case 11:
2027         case 12:
2028         case 13:
2029         case 14:
2030         case 15:
2031         case 18:
2032         case 19:
2033           /* Castling legal iff K & R start in normal positions */
2034           v = VariantNormal;
2035           break;
2036         case 6:
2037         case 20:
2038         case 21:
2039           /* Special wilds for position setup; unclear what to do here */
2040           v = VariantLoadable;
2041           break;
2042         case 9:
2043           /* Bizarre ICC game */
2044           v = VariantTwoKings;
2045           break;
2046         case 16:
2047           v = VariantKriegspiel;
2048           break;
2049         case 17:
2050           v = VariantLosers;
2051           break;
2052         case 22:
2053           v = VariantFischeRandom;
2054           break;
2055         case 23:
2056           v = VariantCrazyhouse;
2057           break;
2058         case 24:
2059           v = VariantBughouse;
2060           break;
2061         case 25:
2062           v = Variant3Check;
2063           break;
2064         case 26:
2065           /* Not quite the same as FICS suicide! */
2066           v = VariantGiveaway;
2067           break;
2068         case 27:
2069           v = VariantAtomic;
2070           break;
2071         case 28:
2072           v = VariantShatranj;
2073           break;
2074
2075         /* Temporary names for future ICC types.  The name *will* change in
2076            the next xboard/WinBoard release after ICC defines it. */
2077         case 29:
2078           v = Variant29;
2079           break;
2080         case 30:
2081           v = Variant30;
2082           break;
2083         case 31:
2084           v = Variant31;
2085           break;
2086         case 32:
2087           v = Variant32;
2088           break;
2089         case 33:
2090           v = Variant33;
2091           break;
2092         case 34:
2093           v = Variant34;
2094           break;
2095         case 35:
2096           v = Variant35;
2097           break;
2098         case 36:
2099           v = Variant36;
2100           break;
2101         case 37:
2102           v = VariantShogi;
2103           break;
2104         case 38:
2105           v = VariantXiangqi;
2106           break;
2107         case 39:
2108           v = VariantCourier;
2109           break;
2110         case 40:
2111           v = VariantGothic;
2112           break;
2113         case 41:
2114           v = VariantCapablanca;
2115           break;
2116         case 42:
2117           v = VariantKnightmate;
2118           break;
2119         case 43:
2120           v = VariantFairy;
2121           break;
2122         case 44:
2123           v = VariantCylinder;
2124           break;
2125         case 45:
2126           v = VariantFalcon;
2127           break;
2128         case 46:
2129           v = VariantCapaRandom;
2130           break;
2131         case 47:
2132           v = VariantBerolina;
2133           break;
2134         case 48:
2135           v = VariantJanus;
2136           break;
2137         case 49:
2138           v = VariantSuper;
2139           break;
2140         case 50:
2141           v = VariantGreat;
2142           break;
2143         case -1:
2144           /* Found "wild" or "w" in the string but no number;
2145              must assume it's normal chess. */
2146           v = VariantNormal;
2147           break;
2148         default:
2149           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2150           if( (len > MSG_SIZ) && appData.debugMode )
2151             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2152
2153           DisplayError(buf, 0);
2154           v = VariantUnknown;
2155           break;
2156         }
2157       }
2158     }
2159     if (appData.debugMode) {
2160       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2161               e, wnum, VariantName(v));
2162     }
2163     return v;
2164 }
2165
2166 static int leftover_start = 0, leftover_len = 0;
2167 char star_match[STAR_MATCH_N][MSG_SIZ];
2168
2169 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2170    advance *index beyond it, and set leftover_start to the new value of
2171    *index; else return FALSE.  If pattern contains the character '*', it
2172    matches any sequence of characters not containing '\r', '\n', or the
2173    character following the '*' (if any), and the matched sequence(s) are
2174    copied into star_match.
2175    */
2176 int
2177 looking_at(buf, index, pattern)
2178      char *buf;
2179      int *index;
2180      char *pattern;
2181 {
2182     char *bufp = &buf[*index], *patternp = pattern;
2183     int star_count = 0;
2184     char *matchp = star_match[0];
2185
2186     for (;;) {
2187         if (*patternp == NULLCHAR) {
2188             *index = leftover_start = bufp - buf;
2189             *matchp = NULLCHAR;
2190             return TRUE;
2191         }
2192         if (*bufp == NULLCHAR) return FALSE;
2193         if (*patternp == '*') {
2194             if (*bufp == *(patternp + 1)) {
2195                 *matchp = NULLCHAR;
2196                 matchp = star_match[++star_count];
2197                 patternp += 2;
2198                 bufp++;
2199                 continue;
2200             } else if (*bufp == '\n' || *bufp == '\r') {
2201                 patternp++;
2202                 if (*patternp == NULLCHAR)
2203                   continue;
2204                 else
2205                   return FALSE;
2206             } else {
2207                 *matchp++ = *bufp++;
2208                 continue;
2209             }
2210         }
2211         if (*patternp != *bufp) return FALSE;
2212         patternp++;
2213         bufp++;
2214     }
2215 }
2216
2217 void
2218 SendToPlayer(data, length)
2219      char *data;
2220      int length;
2221 {
2222     int error, outCount;
2223     outCount = OutputToProcess(NoProc, data, length, &error);
2224     if (outCount < length) {
2225         DisplayFatalError(_("Error writing to display"), error, 1);
2226     }
2227 }
2228
2229 void
2230 PackHolding(packed, holding)
2231      char packed[];
2232      char *holding;
2233 {
2234     char *p = holding;
2235     char *q = packed;
2236     int runlength = 0;
2237     int curr = 9999;
2238     do {
2239         if (*p == curr) {
2240             runlength++;
2241         } else {
2242             switch (runlength) {
2243               case 0:
2244                 break;
2245               case 1:
2246                 *q++ = curr;
2247                 break;
2248               case 2:
2249                 *q++ = curr;
2250                 *q++ = curr;
2251                 break;
2252               default:
2253                 sprintf(q, "%d", runlength);
2254                 while (*q) q++;
2255                 *q++ = curr;
2256                 break;
2257             }
2258             runlength = 1;
2259             curr = *p;
2260         }
2261     } while (*p++);
2262     *q = NULLCHAR;
2263 }
2264
2265 /* Telnet protocol requests from the front end */
2266 void
2267 TelnetRequest(ddww, option)
2268      unsigned char ddww, option;
2269 {
2270     unsigned char msg[3];
2271     int outCount, outError;
2272
2273     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2274
2275     if (appData.debugMode) {
2276         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2277         switch (ddww) {
2278           case TN_DO:
2279             ddwwStr = "DO";
2280             break;
2281           case TN_DONT:
2282             ddwwStr = "DONT";
2283             break;
2284           case TN_WILL:
2285             ddwwStr = "WILL";
2286             break;
2287           case TN_WONT:
2288             ddwwStr = "WONT";
2289             break;
2290           default:
2291             ddwwStr = buf1;
2292             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2293             break;
2294         }
2295         switch (option) {
2296           case TN_ECHO:
2297             optionStr = "ECHO";
2298             break;
2299           default:
2300             optionStr = buf2;
2301             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2302             break;
2303         }
2304         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2305     }
2306     msg[0] = TN_IAC;
2307     msg[1] = ddww;
2308     msg[2] = option;
2309     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2310     if (outCount < 3) {
2311         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2312     }
2313 }
2314
2315 void
2316 DoEcho()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DO, TN_ECHO);
2320 }
2321
2322 void
2323 DontEcho()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DONT, TN_ECHO);
2327 }
2328
2329 void
2330 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2331 {
2332     /* put the holdings sent to us by the server on the board holdings area */
2333     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2334     char p;
2335     ChessSquare piece;
2336
2337     if(gameInfo.holdingsWidth < 2)  return;
2338     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2339         return; // prevent overwriting by pre-board holdings
2340
2341     if( (int)lowestPiece >= BlackPawn ) {
2342         holdingsColumn = 0;
2343         countsColumn = 1;
2344         holdingsStartRow = BOARD_HEIGHT-1;
2345         direction = -1;
2346     } else {
2347         holdingsColumn = BOARD_WIDTH-1;
2348         countsColumn = BOARD_WIDTH-2;
2349         holdingsStartRow = 0;
2350         direction = 1;
2351     }
2352
2353     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2354         board[i][holdingsColumn] = EmptySquare;
2355         board[i][countsColumn]   = (ChessSquare) 0;
2356     }
2357     while( (p=*holdings++) != NULLCHAR ) {
2358         piece = CharToPiece( ToUpper(p) );
2359         if(piece == EmptySquare) continue;
2360         /*j = (int) piece - (int) WhitePawn;*/
2361         j = PieceToNumber(piece);
2362         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2363         if(j < 0) continue;               /* should not happen */
2364         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2365         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2366         board[holdingsStartRow+j*direction][countsColumn]++;
2367     }
2368 }
2369
2370
2371 void
2372 VariantSwitch(Board board, VariantClass newVariant)
2373 {
2374    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2375    static Board oldBoard;
2376
2377    startedFromPositionFile = FALSE;
2378    if(gameInfo.variant == newVariant) return;
2379
2380    /* [HGM] This routine is called each time an assignment is made to
2381     * gameInfo.variant during a game, to make sure the board sizes
2382     * are set to match the new variant. If that means adding or deleting
2383     * holdings, we shift the playing board accordingly
2384     * This kludge is needed because in ICS observe mode, we get boards
2385     * of an ongoing game without knowing the variant, and learn about the
2386     * latter only later. This can be because of the move list we requested,
2387     * in which case the game history is refilled from the beginning anyway,
2388     * but also when receiving holdings of a crazyhouse game. In the latter
2389     * case we want to add those holdings to the already received position.
2390     */
2391
2392
2393    if (appData.debugMode) {
2394      fprintf(debugFP, "Switch board from %s to %s\n",
2395              VariantName(gameInfo.variant), VariantName(newVariant));
2396      setbuf(debugFP, NULL);
2397    }
2398    shuffleOpenings = 0;       /* [HGM] shuffle */
2399    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2400    switch(newVariant)
2401      {
2402      case VariantShogi:
2403        newWidth = 9;  newHeight = 9;
2404        gameInfo.holdingsSize = 7;
2405      case VariantBughouse:
2406      case VariantCrazyhouse:
2407        newHoldingsWidth = 2; break;
2408      case VariantGreat:
2409        newWidth = 10;
2410      case VariantSuper:
2411        newHoldingsWidth = 2;
2412        gameInfo.holdingsSize = 8;
2413        break;
2414      case VariantGothic:
2415      case VariantCapablanca:
2416      case VariantCapaRandom:
2417        newWidth = 10;
2418      default:
2419        newHoldingsWidth = gameInfo.holdingsSize = 0;
2420      };
2421
2422    if(newWidth  != gameInfo.boardWidth  ||
2423       newHeight != gameInfo.boardHeight ||
2424       newHoldingsWidth != gameInfo.holdingsWidth ) {
2425
2426      /* shift position to new playing area, if needed */
2427      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432        for(i=0; i<newHeight; i++) {
2433          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2434          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2435        }
2436      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2437        for(i=0; i<BOARD_HEIGHT; i++)
2438          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2439            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440              board[i][j];
2441      }
2442      gameInfo.boardWidth  = newWidth;
2443      gameInfo.boardHeight = newHeight;
2444      gameInfo.holdingsWidth = newHoldingsWidth;
2445      gameInfo.variant = newVariant;
2446      InitDrawingSizes(-2, 0);
2447    } else gameInfo.variant = newVariant;
2448    CopyBoard(oldBoard, board);   // remember correctly formatted board
2449      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2450    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2451 }
2452
2453 static int loggedOn = FALSE;
2454
2455 /*-- Game start info cache: --*/
2456 int gs_gamenum;
2457 char gs_kind[MSG_SIZ];
2458 static char player1Name[128] = "";
2459 static char player2Name[128] = "";
2460 static char cont_seq[] = "\n\\   ";
2461 static int player1Rating = -1;
2462 static int player2Rating = -1;
2463 /*----------------------------*/
2464
2465 ColorClass curColor = ColorNormal;
2466 int suppressKibitz = 0;
2467
2468 // [HGM] seekgraph
2469 Boolean soughtPending = FALSE;
2470 Boolean seekGraphUp;
2471 #define MAX_SEEK_ADS 200
2472 #define SQUARE 0x80
2473 char *seekAdList[MAX_SEEK_ADS];
2474 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2475 float tcList[MAX_SEEK_ADS];
2476 char colorList[MAX_SEEK_ADS];
2477 int nrOfSeekAds = 0;
2478 int minRating = 1010, maxRating = 2800;
2479 int hMargin = 10, vMargin = 20, h, w;
2480 extern int squareSize, lineGap;
2481
2482 void
2483 PlotSeekAd(int i)
2484 {
2485         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2486         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2487         if(r < minRating+100 && r >=0 ) r = minRating+100;
2488         if(r > maxRating) r = maxRating;
2489         if(tc < 1.) tc = 1.;
2490         if(tc > 95.) tc = 95.;
2491         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2492         y = ((double)r - minRating)/(maxRating - minRating)
2493             * (h-vMargin-squareSize/8-1) + vMargin;
2494         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2495         if(strstr(seekAdList[i], " u ")) color = 1;
2496         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2497            !strstr(seekAdList[i], "bullet") &&
2498            !strstr(seekAdList[i], "blitz") &&
2499            !strstr(seekAdList[i], "standard") ) color = 2;
2500         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2501         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2502 }
2503
2504 void
2505 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2506 {
2507         char buf[MSG_SIZ], *ext = "";
2508         VariantClass v = StringToVariant(type);
2509         if(strstr(type, "wild")) {
2510             ext = type + 4; // append wild number
2511             if(v == VariantFischeRandom) type = "chess960"; else
2512             if(v == VariantLoadable) type = "setup"; else
2513             type = VariantName(v);
2514         }
2515         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2516         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2517             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2518             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2519             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2520             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2521             seekNrList[nrOfSeekAds] = nr;
2522             zList[nrOfSeekAds] = 0;
2523             seekAdList[nrOfSeekAds++] = StrSave(buf);
2524             if(plot) PlotSeekAd(nrOfSeekAds-1);
2525         }
2526 }
2527
2528 void
2529 EraseSeekDot(int i)
2530 {
2531     int x = xList[i], y = yList[i], d=squareSize/4, k;
2532     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2533     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2534     // now replot every dot that overlapped
2535     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2536         int xx = xList[k], yy = yList[k];
2537         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2538             DrawSeekDot(xx, yy, colorList[k]);
2539     }
2540 }
2541
2542 void
2543 RemoveSeekAd(int nr)
2544 {
2545         int i;
2546         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2547             EraseSeekDot(i);
2548             if(seekAdList[i]) free(seekAdList[i]);
2549             seekAdList[i] = seekAdList[--nrOfSeekAds];
2550             seekNrList[i] = seekNrList[nrOfSeekAds];
2551             ratingList[i] = ratingList[nrOfSeekAds];
2552             colorList[i]  = colorList[nrOfSeekAds];
2553             tcList[i] = tcList[nrOfSeekAds];
2554             xList[i]  = xList[nrOfSeekAds];
2555             yList[i]  = yList[nrOfSeekAds];
2556             zList[i]  = zList[nrOfSeekAds];
2557             seekAdList[nrOfSeekAds] = NULL;
2558             break;
2559         }
2560 }
2561
2562 Boolean
2563 MatchSoughtLine(char *line)
2564 {
2565     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2566     int nr, base, inc, u=0; char dummy;
2567
2568     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2570        (u=1) &&
2571        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2573         // match: compact and save the line
2574         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2575         return TRUE;
2576     }
2577     return FALSE;
2578 }
2579
2580 int
2581 DrawSeekGraph()
2582 {
2583     int i;
2584     if(!seekGraphUp) return FALSE;
2585     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2586     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2587
2588     DrawSeekBackground(0, 0, w, h);
2589     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2590     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2591     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2592         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2593         yy = h-1-yy;
2594         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2595         if(i%500 == 0) {
2596             char buf[MSG_SIZ];
2597             snprintf(buf, MSG_SIZ, "%d", i);
2598             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2599         }
2600     }
2601     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2602     for(i=1; i<100; i+=(i<10?1:5)) {
2603         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2604         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2605         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2606             char buf[MSG_SIZ];
2607             snprintf(buf, MSG_SIZ, "%d", i);
2608             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2609         }
2610     }
2611     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2612     return TRUE;
2613 }
2614
2615 int SeekGraphClick(ClickType click, int x, int y, int moving)
2616 {
2617     static int lastDown = 0, displayed = 0, lastSecond;
2618     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2619         if(click == Release || moving) return FALSE;
2620         nrOfSeekAds = 0;
2621         soughtPending = TRUE;
2622         SendToICS(ics_prefix);
2623         SendToICS("sought\n"); // should this be "sought all"?
2624     } else { // issue challenge based on clicked ad
2625         int dist = 10000; int i, closest = 0, second = 0;
2626         for(i=0; i<nrOfSeekAds; i++) {
2627             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2628             if(d < dist) { dist = d; closest = i; }
2629             second += (d - zList[i] < 120); // count in-range ads
2630             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2631         }
2632         if(dist < 120) {
2633             char buf[MSG_SIZ];
2634             second = (second > 1);
2635             if(displayed != closest || second != lastSecond) {
2636                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2637                 lastSecond = second; displayed = closest;
2638             }
2639             if(click == Press) {
2640                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2641                 lastDown = closest;
2642                 return TRUE;
2643             } // on press 'hit', only show info
2644             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2645             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2646             SendToICS(ics_prefix);
2647             SendToICS(buf);
2648             return TRUE; // let incoming board of started game pop down the graph
2649         } else if(click == Release) { // release 'miss' is ignored
2650             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2651             if(moving == 2) { // right up-click
2652                 nrOfSeekAds = 0; // refresh graph
2653                 soughtPending = TRUE;
2654                 SendToICS(ics_prefix);
2655                 SendToICS("sought\n"); // should this be "sought all"?
2656             }
2657             return TRUE;
2658         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2659         // press miss or release hit 'pop down' seek graph
2660         seekGraphUp = FALSE;
2661         DrawPosition(TRUE, NULL);
2662     }
2663     return TRUE;
2664 }
2665
2666 void
2667 read_from_ics(isr, closure, data, count, error)
2668      InputSourceRef isr;
2669      VOIDSTAR closure;
2670      char *data;
2671      int count;
2672      int error;
2673 {
2674 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2675 #define STARTED_NONE 0
2676 #define STARTED_MOVES 1
2677 #define STARTED_BOARD 2
2678 #define STARTED_OBSERVE 3
2679 #define STARTED_HOLDINGS 4
2680 #define STARTED_CHATTER 5
2681 #define STARTED_COMMENT 6
2682 #define STARTED_MOVES_NOHIDE 7
2683
2684     static int started = STARTED_NONE;
2685     static char parse[20000];
2686     static int parse_pos = 0;
2687     static char buf[BUF_SIZE + 1];
2688     static int firstTime = TRUE, intfSet = FALSE;
2689     static ColorClass prevColor = ColorNormal;
2690     static int savingComment = FALSE;
2691     static int cmatch = 0; // continuation sequence match
2692     char *bp;
2693     char str[MSG_SIZ];
2694     int i, oldi;
2695     int buf_len;
2696     int next_out;
2697     int tkind;
2698     int backup;    /* [DM] For zippy color lines */
2699     char *p;
2700     char talker[MSG_SIZ]; // [HGM] chat
2701     int channel;
2702
2703     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2704
2705     if (appData.debugMode) {
2706       if (!error) {
2707         fprintf(debugFP, "<ICS: ");
2708         show_bytes(debugFP, data, count);
2709         fprintf(debugFP, "\n");
2710       }
2711     }
2712
2713     if (appData.debugMode) { int f = forwardMostMove;
2714         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2715                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2716                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2717     }
2718     if (count > 0) {
2719         /* If last read ended with a partial line that we couldn't parse,
2720            prepend it to the new read and try again. */
2721         if (leftover_len > 0) {
2722             for (i=0; i<leftover_len; i++)
2723               buf[i] = buf[leftover_start + i];
2724         }
2725
2726     /* copy new characters into the buffer */
2727     bp = buf + leftover_len;
2728     buf_len=leftover_len;
2729     for (i=0; i<count; i++)
2730     {
2731         // ignore these
2732         if (data[i] == '\r')
2733             continue;
2734
2735         // join lines split by ICS?
2736         if (!appData.noJoin)
2737         {
2738             /*
2739                 Joining just consists of finding matches against the
2740                 continuation sequence, and discarding that sequence
2741                 if found instead of copying it.  So, until a match
2742                 fails, there's nothing to do since it might be the
2743                 complete sequence, and thus, something we don't want
2744                 copied.
2745             */
2746             if (data[i] == cont_seq[cmatch])
2747             {
2748                 cmatch++;
2749                 if (cmatch == strlen(cont_seq))
2750                 {
2751                     cmatch = 0; // complete match.  just reset the counter
2752
2753                     /*
2754                         it's possible for the ICS to not include the space
2755                         at the end of the last word, making our [correct]
2756                         join operation fuse two separate words.  the server
2757                         does this when the space occurs at the width setting.
2758                     */
2759                     if (!buf_len || buf[buf_len-1] != ' ')
2760                     {
2761                         *bp++ = ' ';
2762                         buf_len++;
2763                     }
2764                 }
2765                 continue;
2766             }
2767             else if (cmatch)
2768             {
2769                 /*
2770                     match failed, so we have to copy what matched before
2771                     falling through and copying this character.  In reality,
2772                     this will only ever be just the newline character, but
2773                     it doesn't hurt to be precise.
2774                 */
2775                 strncpy(bp, cont_seq, cmatch);
2776                 bp += cmatch;
2777                 buf_len += cmatch;
2778                 cmatch = 0;
2779             }
2780         }
2781
2782         // copy this char
2783         *bp++ = data[i];
2784         buf_len++;
2785     }
2786
2787         buf[buf_len] = NULLCHAR;
2788 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2789         next_out = 0;
2790         leftover_start = 0;
2791
2792         i = 0;
2793         while (i < buf_len) {
2794             /* Deal with part of the TELNET option negotiation
2795                protocol.  We refuse to do anything beyond the
2796                defaults, except that we allow the WILL ECHO option,
2797                which ICS uses to turn off password echoing when we are
2798                directly connected to it.  We reject this option
2799                if localLineEditing mode is on (always on in xboard)
2800                and we are talking to port 23, which might be a real
2801                telnet server that will try to keep WILL ECHO on permanently.
2802              */
2803             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2804                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2805                 unsigned char option;
2806                 oldi = i;
2807                 switch ((unsigned char) buf[++i]) {
2808                   case TN_WILL:
2809                     if (appData.debugMode)
2810                       fprintf(debugFP, "\n<WILL ");
2811                     switch (option = (unsigned char) buf[++i]) {
2812                       case TN_ECHO:
2813                         if (appData.debugMode)
2814                           fprintf(debugFP, "ECHO ");
2815                         /* Reply only if this is a change, according
2816                            to the protocol rules. */
2817                         if (remoteEchoOption) break;
2818                         if (appData.localLineEditing &&
2819                             atoi(appData.icsPort) == TN_PORT) {
2820                             TelnetRequest(TN_DONT, TN_ECHO);
2821                         } else {
2822                             EchoOff();
2823                             TelnetRequest(TN_DO, TN_ECHO);
2824                             remoteEchoOption = TRUE;
2825                         }
2826                         break;
2827                       default:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "%d ", option);
2830                         /* Whatever this is, we don't want it. */
2831                         TelnetRequest(TN_DONT, option);
2832                         break;
2833                     }
2834                     break;
2835                   case TN_WONT:
2836                     if (appData.debugMode)
2837                       fprintf(debugFP, "\n<WONT ");
2838                     switch (option = (unsigned char) buf[++i]) {
2839                       case TN_ECHO:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "ECHO ");
2842                         /* Reply only if this is a change, according
2843                            to the protocol rules. */
2844                         if (!remoteEchoOption) break;
2845                         EchoOn();
2846                         TelnetRequest(TN_DONT, TN_ECHO);
2847                         remoteEchoOption = FALSE;
2848                         break;
2849                       default:
2850                         if (appData.debugMode)
2851                           fprintf(debugFP, "%d ", (unsigned char) option);
2852                         /* Whatever this is, it must already be turned
2853                            off, because we never agree to turn on
2854                            anything non-default, so according to the
2855                            protocol rules, we don't reply. */
2856                         break;
2857                     }
2858                     break;
2859                   case TN_DO:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<DO ");
2862                     switch (option = (unsigned char) buf[++i]) {
2863                       default:
2864                         /* Whatever this is, we refuse to do it. */
2865                         if (appData.debugMode)
2866                           fprintf(debugFP, "%d ", option);
2867                         TelnetRequest(TN_WONT, option);
2868                         break;
2869                     }
2870                     break;
2871                   case TN_DONT:
2872                     if (appData.debugMode)
2873                       fprintf(debugFP, "\n<DONT ");
2874                     switch (option = (unsigned char) buf[++i]) {
2875                       default:
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         /* Whatever this is, we are already not doing
2879                            it, because we never agree to do anything
2880                            non-default, so according to the protocol
2881                            rules, we don't reply. */
2882                         break;
2883                     }
2884                     break;
2885                   case TN_IAC:
2886                     if (appData.debugMode)
2887                       fprintf(debugFP, "\n<IAC ");
2888                     /* Doubled IAC; pass it through */
2889                     i--;
2890                     break;
2891                   default:
2892                     if (appData.debugMode)
2893                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2894                     /* Drop all other telnet commands on the floor */
2895                     break;
2896                 }
2897                 if (oldi > next_out)
2898                   SendToPlayer(&buf[next_out], oldi - next_out);
2899                 if (++i > next_out)
2900                   next_out = i;
2901                 continue;
2902             }
2903
2904             /* OK, this at least will *usually* work */
2905             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2906                 loggedOn = TRUE;
2907             }
2908
2909             if (loggedOn && !intfSet) {
2910                 if (ics_type == ICS_ICC) {
2911                   snprintf(str, MSG_SIZ,
2912                           "/set-quietly interface %s\n/set-quietly style 12\n",
2913                           programVersion);
2914                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2916                 } else if (ics_type == ICS_CHESSNET) {
2917                   snprintf(str, MSG_SIZ, "/style 12\n");
2918                 } else {
2919                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2920                   strcat(str, programVersion);
2921                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2922                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2923                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2924 #ifdef WIN32
2925                   strcat(str, "$iset nohighlight 1\n");
2926 #endif
2927                   strcat(str, "$iset lock 1\n$style 12\n");
2928                 }
2929                 SendToICS(str);
2930                 NotifyFrontendLogin();
2931                 intfSet = TRUE;
2932             }
2933
2934             if (started == STARTED_COMMENT) {
2935                 /* Accumulate characters in comment */
2936                 parse[parse_pos++] = buf[i];
2937                 if (buf[i] == '\n') {
2938                     parse[parse_pos] = NULLCHAR;
2939                     if(chattingPartner>=0) {
2940                         char mess[MSG_SIZ];
2941                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2942                         OutputChatMessage(chattingPartner, mess);
2943                         chattingPartner = -1;
2944                         next_out = i+1; // [HGM] suppress printing in ICS window
2945                     } else
2946                     if(!suppressKibitz) // [HGM] kibitz
2947                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2948                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2949                         int nrDigit = 0, nrAlph = 0, j;
2950                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2951                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2952                         parse[parse_pos] = NULLCHAR;
2953                         // try to be smart: if it does not look like search info, it should go to
2954                         // ICS interaction window after all, not to engine-output window.
2955                         for(j=0; j<parse_pos; j++) { // count letters and digits
2956                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2957                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2958                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2959                         }
2960                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2961                             int depth=0; float score;
2962                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2963                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2964                                 pvInfoList[forwardMostMove-1].depth = depth;
2965                                 pvInfoList[forwardMostMove-1].score = 100*score;
2966                             }
2967                             OutputKibitz(suppressKibitz, parse);
2968                         } else {
2969                             char tmp[MSG_SIZ];
2970                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2971                             SendToPlayer(tmp, strlen(tmp));
2972                         }
2973                         next_out = i+1; // [HGM] suppress printing in ICS window
2974                     }
2975                     started = STARTED_NONE;
2976                 } else {
2977                     /* Don't match patterns against characters in comment */
2978                     i++;
2979                     continue;
2980                 }
2981             }
2982             if (started == STARTED_CHATTER) {
2983                 if (buf[i] != '\n') {
2984                     /* Don't match patterns against characters in chatter */
2985                     i++;
2986                     continue;
2987                 }
2988                 started = STARTED_NONE;
2989                 if(suppressKibitz) next_out = i+1;
2990             }
2991
2992             /* Kludge to deal with rcmd protocol */
2993             if (firstTime && looking_at(buf, &i, "\001*")) {
2994                 DisplayFatalError(&buf[1], 0, 1);
2995                 continue;
2996             } else {
2997                 firstTime = FALSE;
2998             }
2999
3000             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3001                 ics_type = ICS_ICC;
3002                 ics_prefix = "/";
3003                 if (appData.debugMode)
3004                   fprintf(debugFP, "ics_type %d\n", ics_type);
3005                 continue;
3006             }
3007             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3008                 ics_type = ICS_FICS;
3009                 ics_prefix = "$";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3015                 ics_type = ICS_CHESSNET;
3016                 ics_prefix = "/";
3017                 if (appData.debugMode)
3018                   fprintf(debugFP, "ics_type %d\n", ics_type);
3019                 continue;
3020             }
3021
3022             if (!loggedOn &&
3023                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3024                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3025                  looking_at(buf, &i, "will be \"*\""))) {
3026               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3027               continue;
3028             }
3029
3030             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3031               char buf[MSG_SIZ];
3032               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3033               DisplayIcsInteractionTitle(buf);
3034               have_set_title = TRUE;
3035             }
3036
3037             /* skip finger notes */
3038             if (started == STARTED_NONE &&
3039                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3040                  (buf[i] == '1' && buf[i+1] == '0')) &&
3041                 buf[i+2] == ':' && buf[i+3] == ' ') {
3042               started = STARTED_CHATTER;
3043               i += 3;
3044               continue;
3045             }
3046
3047             oldi = i;
3048             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3049             if(appData.seekGraph) {
3050                 if(soughtPending && MatchSoughtLine(buf+i)) {
3051                     i = strstr(buf+i, "rated") - buf;
3052                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                     next_out = leftover_start = i;
3054                     started = STARTED_CHATTER;
3055                     suppressKibitz = TRUE;
3056                     continue;
3057                 }
3058                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3059                         && looking_at(buf, &i, "* ads displayed")) {
3060                     soughtPending = FALSE;
3061                     seekGraphUp = TRUE;
3062                     DrawSeekGraph();
3063                     continue;
3064                 }
3065                 if(appData.autoRefresh) {
3066                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3067                         int s = (ics_type == ICS_ICC); // ICC format differs
3068                         if(seekGraphUp)
3069                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3070                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3071                         looking_at(buf, &i, "*% "); // eat prompt
3072                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3073                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                         next_out = i; // suppress
3075                         continue;
3076                     }
3077                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3078                         char *p = star_match[0];
3079                         while(*p) {
3080                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3081                             while(*p && *p++ != ' '); // next
3082                         }
3083                         looking_at(buf, &i, "*% "); // eat prompt
3084                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = i;
3086                         continue;
3087                     }
3088                 }
3089             }
3090
3091             /* skip formula vars */
3092             if (started == STARTED_NONE &&
3093                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3094               started = STARTED_CHATTER;
3095               i += 3;
3096               continue;
3097             }
3098
3099             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3100             if (appData.autoKibitz && started == STARTED_NONE &&
3101                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3102                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3103                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3104                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3105                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3106                         suppressKibitz = TRUE;
3107                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                         next_out = i;
3109                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3110                                 && (gameMode == IcsPlayingWhite)) ||
3111                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3112                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3113                             started = STARTED_CHATTER; // own kibitz we simply discard
3114                         else {
3115                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3116                             parse_pos = 0; parse[0] = NULLCHAR;
3117                             savingComment = TRUE;
3118                             suppressKibitz = gameMode != IcsObserving ? 2 :
3119                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3120                         }
3121                         continue;
3122                 } else
3123                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3124                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3125                          && atoi(star_match[0])) {
3126                     // suppress the acknowledgements of our own autoKibitz
3127                     char *p;
3128                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3129                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3130                     SendToPlayer(star_match[0], strlen(star_match[0]));
3131                     if(looking_at(buf, &i, "*% ")) // eat prompt
3132                         suppressKibitz = FALSE;
3133                     next_out = i;
3134                     continue;
3135                 }
3136             } // [HGM] kibitz: end of patch
3137
3138             // [HGM] chat: intercept tells by users for which we have an open chat window
3139             channel = -1;
3140             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141                                            looking_at(buf, &i, "* whispers:") ||
3142                                            looking_at(buf, &i, "* kibitzes:") ||
3143                                            looking_at(buf, &i, "* shouts:") ||
3144                                            looking_at(buf, &i, "* c-shouts:") ||
3145                                            looking_at(buf, &i, "--> * ") ||
3146                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3150                 int p;
3151                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152                 chattingPartner = -1;
3153
3154                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155                 for(p=0; p<MAX_CHAT; p++) {
3156                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157                     talker[0] = '['; strcat(talker, "] ");
3158                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159                     chattingPartner = p; break;
3160                     }
3161                 } else
3162                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163                 for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("kibitzes", chatPartner[p])) {
3165                         talker[0] = '['; strcat(talker, "] ");
3166                         chattingPartner = p; break;
3167                     }
3168                 } else
3169                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170                 for(p=0; p<MAX_CHAT; p++) {
3171                     if(!strcmp("whispers", chatPartner[p])) {
3172                         talker[0] = '['; strcat(talker, "] ");
3173                         chattingPartner = p; break;
3174                     }
3175                 } else
3176                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177                   if(buf[i-8] == '-' && buf[i-3] == 't')
3178                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179                     if(!strcmp("c-shouts", chatPartner[p])) {
3180                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                   if(chattingPartner < 0)
3185                   for(p=0; p<MAX_CHAT; p++) {
3186                     if(!strcmp("shouts", chatPartner[p])) {
3187                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190                         chattingPartner = p; break;
3191                     }
3192                   }
3193                 }
3194                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196                     talker[0] = 0; Colorize(ColorTell, FALSE);
3197                     chattingPartner = p; break;
3198                 }
3199                 if(chattingPartner<0) i = oldi; else {
3200                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203                     started = STARTED_COMMENT;
3204                     parse_pos = 0; parse[0] = NULLCHAR;
3205                     savingComment = 3 + chattingPartner; // counts as TRUE
3206                     suppressKibitz = TRUE;
3207                     continue;
3208                 }
3209             } // [HGM] chat: end of patch
3210
3211           backup = i;
3212             if (appData.zippyTalk || appData.zippyPlay) {
3213                 /* [DM] Backup address for color zippy lines */
3214 #if ZIPPY
3215                if (loggedOn == TRUE)
3216                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3218 #endif
3219             } // [DM] 'else { ' deleted
3220                 if (
3221                     /* Regular tells and says */
3222                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3224                     looking_at(buf, &i, "* says: ") ||
3225                     /* Don't color "message" or "messages" output */
3226                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227                     looking_at(buf, &i, "*. * at *:*: ") ||
3228                     looking_at(buf, &i, "--* (*:*): ") ||
3229                     /* Message notifications (same color as tells) */
3230                     looking_at(buf, &i, "* has left a message ") ||
3231                     looking_at(buf, &i, "* just sent you a message:\n") ||
3232                     /* Whispers and kibitzes */
3233                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234                     looking_at(buf, &i, "* kibitzes: ") ||
3235                     /* Channel tells */
3236                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3237
3238                   if (tkind == 1 && strchr(star_match[0], ':')) {
3239                       /* Avoid "tells you:" spoofs in channels */
3240                      tkind = 3;
3241                   }
3242                   if (star_match[0][0] == NULLCHAR ||
3243                       strchr(star_match[0], ' ') ||
3244                       (tkind == 3 && strchr(star_match[1], ' '))) {
3245                     /* Reject bogus matches */
3246                     i = oldi;
3247                   } else {
3248                     if (appData.colorize) {
3249                       if (oldi > next_out) {
3250                         SendToPlayer(&buf[next_out], oldi - next_out);
3251                         next_out = oldi;
3252                       }
3253                       switch (tkind) {
3254                       case 1:
3255                         Colorize(ColorTell, FALSE);
3256                         curColor = ColorTell;
3257                         break;
3258                       case 2:
3259                         Colorize(ColorKibitz, FALSE);
3260                         curColor = ColorKibitz;
3261                         break;
3262                       case 3:
3263                         p = strrchr(star_match[1], '(');
3264                         if (p == NULL) {
3265                           p = star_match[1];
3266                         } else {
3267                           p++;
3268                         }
3269                         if (atoi(p) == 1) {
3270                           Colorize(ColorChannel1, FALSE);
3271                           curColor = ColorChannel1;
3272                         } else {
3273                           Colorize(ColorChannel, FALSE);
3274                           curColor = ColorChannel;
3275                         }
3276                         break;
3277                       case 5:
3278                         curColor = ColorNormal;
3279                         break;
3280                       }
3281                     }
3282                     if (started == STARTED_NONE && appData.autoComment &&
3283                         (gameMode == IcsObserving ||
3284                          gameMode == IcsPlayingWhite ||
3285                          gameMode == IcsPlayingBlack)) {
3286                       parse_pos = i - oldi;
3287                       memcpy(parse, &buf[oldi], parse_pos);
3288                       parse[parse_pos] = NULLCHAR;
3289                       started = STARTED_COMMENT;
3290                       savingComment = TRUE;
3291                     } else {
3292                       started = STARTED_CHATTER;
3293                       savingComment = FALSE;
3294                     }
3295                     loggedOn = TRUE;
3296                     continue;
3297                   }
3298                 }
3299
3300                 if (looking_at(buf, &i, "* s-shouts: ") ||
3301                     looking_at(buf, &i, "* c-shouts: ")) {
3302                     if (appData.colorize) {
3303                         if (oldi > next_out) {
3304                             SendToPlayer(&buf[next_out], oldi - next_out);
3305                             next_out = oldi;
3306                         }
3307                         Colorize(ColorSShout, FALSE);
3308                         curColor = ColorSShout;
3309                     }
3310                     loggedOn = TRUE;
3311                     started = STARTED_CHATTER;
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "--->")) {
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* shouts: ") ||
3321                     looking_at(buf, &i, "--> ")) {
3322                     if (appData.colorize) {
3323                         if (oldi > next_out) {
3324                             SendToPlayer(&buf[next_out], oldi - next_out);
3325                             next_out = oldi;
3326                         }
3327                         Colorize(ColorShout, FALSE);
3328                         curColor = ColorShout;
3329                     }
3330                     loggedOn = TRUE;
3331                     started = STARTED_CHATTER;
3332                     continue;
3333                 }
3334
3335                 if (looking_at( buf, &i, "Challenge:")) {
3336                     if (appData.colorize) {
3337                         if (oldi > next_out) {
3338                             SendToPlayer(&buf[next_out], oldi - next_out);
3339                             next_out = oldi;
3340                         }
3341                         Colorize(ColorChallenge, FALSE);
3342                         curColor = ColorChallenge;
3343                     }
3344                     loggedOn = TRUE;
3345                     continue;
3346                 }
3347
3348                 if (looking_at(buf, &i, "* offers you") ||
3349                     looking_at(buf, &i, "* offers to be") ||
3350                     looking_at(buf, &i, "* would like to") ||
3351                     looking_at(buf, &i, "* requests to") ||
3352                     looking_at(buf, &i, "Your opponent offers") ||
3353                     looking_at(buf, &i, "Your opponent requests")) {
3354
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorRequest, FALSE);
3361                         curColor = ColorRequest;
3362                     }
3363                     continue;
3364                 }
3365
3366                 if (looking_at(buf, &i, "* (*) seeking")) {
3367                     if (appData.colorize) {
3368                         if (oldi > next_out) {
3369                             SendToPlayer(&buf[next_out], oldi - next_out);
3370                             next_out = oldi;
3371                         }
3372                         Colorize(ColorSeek, FALSE);
3373                         curColor = ColorSeek;
3374                     }
3375                     continue;
3376             }
3377
3378           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3379
3380             if (looking_at(buf, &i, "\\   ")) {
3381                 if (prevColor != ColorNormal) {
3382                     if (oldi > next_out) {
3383                         SendToPlayer(&buf[next_out], oldi - next_out);
3384                         next_out = oldi;
3385                     }
3386                     Colorize(prevColor, TRUE);
3387                     curColor = prevColor;
3388                 }
3389                 if (savingComment) {
3390                     parse_pos = i - oldi;
3391                     memcpy(parse, &buf[oldi], parse_pos);
3392                     parse[parse_pos] = NULLCHAR;
3393                     started = STARTED_COMMENT;
3394                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395                         chattingPartner = savingComment - 3; // kludge to remember the box
3396                 } else {
3397                     started = STARTED_CHATTER;
3398                 }
3399                 continue;
3400             }
3401
3402             if (looking_at(buf, &i, "Black Strength :") ||
3403                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404                 looking_at(buf, &i, "<10>") ||
3405                 looking_at(buf, &i, "#@#")) {
3406                 /* Wrong board style */
3407                 loggedOn = TRUE;
3408                 SendToICS(ics_prefix);
3409                 SendToICS("set style 12\n");
3410                 SendToICS(ics_prefix);
3411                 SendToICS("refresh\n");
3412                 continue;
3413             }
3414
3415             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3416                 ICSInitScript();
3417                 have_sent_ICS_logon = 1;
3418                 continue;
3419             }
3420
3421             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422                 (looking_at(buf, &i, "\n<12> ") ||
3423                  looking_at(buf, &i, "<12> "))) {
3424                 loggedOn = TRUE;
3425                 if (oldi > next_out) {
3426                     SendToPlayer(&buf[next_out], oldi - next_out);
3427                 }
3428                 next_out = i;
3429                 started = STARTED_BOARD;
3430                 parse_pos = 0;
3431                 continue;
3432             }
3433
3434             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435                 looking_at(buf, &i, "<b1> ")) {
3436                 if (oldi > next_out) {
3437                     SendToPlayer(&buf[next_out], oldi - next_out);
3438                 }
3439                 next_out = i;
3440                 started = STARTED_HOLDINGS;
3441                 parse_pos = 0;
3442                 continue;
3443             }
3444
3445             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3446                 loggedOn = TRUE;
3447                 /* Header for a move list -- first line */
3448
3449                 switch (ics_getting_history) {
3450                   case H_FALSE:
3451                     switch (gameMode) {
3452                       case IcsIdle:
3453                       case BeginningOfGame:
3454                         /* User typed "moves" or "oldmoves" while we
3455                            were idle.  Pretend we asked for these
3456                            moves and soak them up so user can step
3457                            through them and/or save them.
3458                            */
3459                         Reset(FALSE, TRUE);
3460                         gameMode = IcsObserving;
3461                         ModeHighlight();
3462                         ics_gamenum = -1;
3463                         ics_getting_history = H_GOT_UNREQ_HEADER;
3464                         break;
3465                       case EditGame: /*?*/
3466                       case EditPosition: /*?*/
3467                         /* Should above feature work in these modes too? */
3468                         /* For now it doesn't */
3469                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3470                         break;
3471                       default:
3472                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3473                         break;
3474                     }
3475                     break;
3476                   case H_REQUESTED:
3477                     /* Is this the right one? */
3478                     if (gameInfo.white && gameInfo.black &&
3479                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3480                         strcmp(gameInfo.black, star_match[2]) == 0) {
3481                         /* All is well */
3482                         ics_getting_history = H_GOT_REQ_HEADER;
3483                     }
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                   case H_GOT_UNREQ_HEADER:
3487                   case H_GOT_UNWANTED_HEADER:
3488                   case H_GETTING_MOVES:
3489                     /* Should not happen */
3490                     DisplayError(_("Error gathering move list: two headers"), 0);
3491                     ics_getting_history = H_FALSE;
3492                     break;
3493                 }
3494
3495                 /* Save player ratings into gameInfo if needed */
3496                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498                     (gameInfo.whiteRating == -1 ||
3499                      gameInfo.blackRating == -1)) {
3500
3501                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3502                     gameInfo.blackRating = string_to_rating(star_match[3]);
3503                     if (appData.debugMode)
3504                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505                               gameInfo.whiteRating, gameInfo.blackRating);
3506                 }
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i,
3511               "* * match, initial time: * minute*, increment: * second")) {
3512                 /* Header for a move list -- second line */
3513                 /* Initial board will follow if this is a wild game */
3514                 if (gameInfo.event != NULL) free(gameInfo.event);
3515                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516                 gameInfo.event = StrSave(str);
3517                 /* [HGM] we switched variant. Translate boards if needed. */
3518                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3519                 continue;
3520             }
3521
3522             if (looking_at(buf, &i, "Move  ")) {
3523                 /* Beginning of a move list */
3524                 switch (ics_getting_history) {
3525                   case H_FALSE:
3526                     /* Normally should not happen */
3527                     /* Maybe user hit reset while we were parsing */
3528                     break;
3529                   case H_REQUESTED:
3530                     /* Happens if we are ignoring a move list that is not
3531                      * the one we just requested.  Common if the user
3532                      * tries to observe two games without turning off
3533                      * getMoveList */
3534                     break;
3535                   case H_GETTING_MOVES:
3536                     /* Should not happen */
3537                     DisplayError(_("Error gathering move list: nested"), 0);
3538                     ics_getting_history = H_FALSE;
3539                     break;
3540                   case H_GOT_REQ_HEADER:
3541                     ics_getting_history = H_GETTING_MOVES;
3542                     started = STARTED_MOVES;
3543                     parse_pos = 0;
3544                     if (oldi > next_out) {
3545                         SendToPlayer(&buf[next_out], oldi - next_out);
3546                     }
3547                     break;
3548                   case H_GOT_UNREQ_HEADER:
3549                     ics_getting_history = H_GETTING_MOVES;
3550                     started = STARTED_MOVES_NOHIDE;
3551                     parse_pos = 0;
3552                     break;
3553                   case H_GOT_UNWANTED_HEADER:
3554                     ics_getting_history = H_FALSE;
3555                     break;
3556                 }
3557                 continue;
3558             }
3559
3560             if (looking_at(buf, &i, "% ") ||
3561                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564                     soughtPending = FALSE;
3565                     seekGraphUp = TRUE;
3566                     DrawSeekGraph();
3567                 }
3568                 if(suppressKibitz) next_out = i;
3569                 savingComment = FALSE;
3570                 suppressKibitz = 0;
3571                 switch (started) {
3572                   case STARTED_MOVES:
3573                   case STARTED_MOVES_NOHIDE:
3574                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575                     parse[parse_pos + i - oldi] = NULLCHAR;
3576                     ParseGameHistory(parse);
3577 #if ZIPPY
3578                     if (appData.zippyPlay && first.initDone) {
3579                         FeedMovesToProgram(&first, forwardMostMove);
3580                         if (gameMode == IcsPlayingWhite) {
3581                             if (WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("black\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, TRUE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, TRUE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         } else if (gameMode == IcsPlayingBlack) {
3605                             if (!WhiteOnMove(forwardMostMove)) {
3606                                 if (first.sendTime) {
3607                                   if (first.useColors) {
3608                                     SendToProgram("white\n", &first);
3609                                   }
3610                                   SendTimeRemaining(&first, FALSE);
3611                                 }
3612                                 if (first.useColors) {
3613                                   SendToProgram("black\n", &first);
3614                                 }
3615                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616                                 first.maybeThinking = TRUE;
3617                             } else {
3618                                 if (first.usePlayother) {
3619                                   if (first.sendTime) {
3620                                     SendTimeRemaining(&first, FALSE);
3621                                   }
3622                                   SendToProgram("playother\n", &first);
3623                                   firstMove = FALSE;
3624                                 } else {
3625                                   firstMove = TRUE;
3626                                 }
3627                             }
3628                         }
3629                     }
3630 #endif
3631                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3632                         /* Moves came from oldmoves or moves command
3633                            while we weren't doing anything else.
3634                            */
3635                         currentMove = forwardMostMove;
3636                         ClearHighlights();/*!!could figure this out*/
3637                         flipView = appData.flipView;
3638                         DrawPosition(TRUE, boards[currentMove]);
3639                         DisplayBothClocks();
3640                         snprintf(str, MSG_SIZ, "%s vs. %s",
3641                                 gameInfo.white, gameInfo.black);
3642                         DisplayTitle(str);
3643                         gameMode = IcsIdle;
3644                     } else {
3645                         /* Moves were history of an active game */
3646                         if (gameInfo.resultDetails != NULL) {
3647                             free(gameInfo.resultDetails);
3648                             gameInfo.resultDetails = NULL;
3649                         }
3650                     }
3651                     HistorySet(parseList, backwardMostMove,
3652                                forwardMostMove, currentMove-1);
3653                     DisplayMove(currentMove - 1);
3654                     if (started == STARTED_MOVES) next_out = i;
3655                     started = STARTED_NONE;
3656                     ics_getting_history = H_FALSE;
3657                     break;
3658
3659                   case STARTED_OBSERVE:
3660                     started = STARTED_NONE;
3661                     SendToICS(ics_prefix);
3662                     SendToICS("refresh\n");
3663                     break;
3664
3665                   default:
3666                     break;
3667                 }
3668                 if(bookHit) { // [HGM] book: simulate book reply
3669                     static char bookMove[MSG_SIZ]; // a bit generous?
3670
3671                     programStats.nodes = programStats.depth = programStats.time =
3672                     programStats.score = programStats.got_only_move = 0;
3673                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3674
3675                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676                     strcat(bookMove, bookHit);
3677                     HandleMachineMove(bookMove, &first);
3678                 }
3679                 continue;
3680             }
3681
3682             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683                  started == STARTED_HOLDINGS ||
3684                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685                 /* Accumulate characters in move list or board */
3686                 parse[parse_pos++] = buf[i];
3687             }
3688
3689             /* Start of game messages.  Mostly we detect start of game
3690                when the first board image arrives.  On some versions
3691                of the ICS, though, we need to do a "refresh" after starting
3692                to observe in order to get the current board right away. */
3693             if (looking_at(buf, &i, "Adding game * to observation list")) {
3694                 started = STARTED_OBSERVE;
3695                 continue;
3696             }
3697
3698             /* Handle auto-observe */
3699             if (appData.autoObserve &&
3700                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3702                 char *player;
3703                 /* Choose the player that was highlighted, if any. */
3704                 if (star_match[0][0] == '\033' ||
3705                     star_match[1][0] != '\033') {
3706                     player = star_match[0];
3707                 } else {
3708                     player = star_match[2];
3709                 }
3710                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711                         ics_prefix, StripHighlightAndTitle(player));
3712                 SendToICS(str);
3713
3714                 /* Save ratings from notify string */
3715                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716                 player1Rating = string_to_rating(star_match[1]);
3717                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718                 player2Rating = string_to_rating(star_match[3]);
3719
3720                 if (appData.debugMode)
3721                   fprintf(debugFP,
3722                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3723                           player1Name, player1Rating,
3724                           player2Name, player2Rating);
3725
3726                 continue;
3727             }
3728
3729             /* Deal with automatic examine mode after a game,
3730                and with IcsObserving -> IcsExamining transition */
3731             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732                 looking_at(buf, &i, "has made you an examiner of game *")) {
3733
3734                 int gamenum = atoi(star_match[0]);
3735                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736                     gamenum == ics_gamenum) {
3737                     /* We were already playing or observing this game;
3738                        no need to refetch history */
3739                     gameMode = IcsExamining;
3740                     if (pausing) {
3741                         pauseExamForwardMostMove = forwardMostMove;
3742                     } else if (currentMove < forwardMostMove) {
3743                         ForwardInner(forwardMostMove);
3744                     }
3745                 } else {
3746                     /* I don't think this case really can happen */
3747                     SendToICS(ics_prefix);
3748                     SendToICS("refresh\n");
3749                 }
3750                 continue;
3751             }
3752
3753             /* Error messages */
3754 //          if (ics_user_moved) {
3755             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756                 if (looking_at(buf, &i, "Illegal move") ||
3757                     looking_at(buf, &i, "Not a legal move") ||
3758                     looking_at(buf, &i, "Your king is in check") ||
3759                     looking_at(buf, &i, "It isn't your turn") ||
3760                     looking_at(buf, &i, "It is not your move")) {
3761                     /* Illegal move */
3762                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763                         currentMove = forwardMostMove-1;
3764                         DisplayMove(currentMove - 1); /* before DMError */
3765                         DrawPosition(FALSE, boards[currentMove]);
3766                         SwitchClocks(forwardMostMove-1); // [HGM] race
3767                         DisplayBothClocks();
3768                     }
3769                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3770                     ics_user_moved = 0;
3771                     continue;
3772                 }
3773             }
3774
3775             if (looking_at(buf, &i, "still have time") ||
3776                 looking_at(buf, &i, "not out of time") ||
3777                 looking_at(buf, &i, "either player is out of time") ||
3778                 looking_at(buf, &i, "has timeseal; checking")) {
3779                 /* We must have called his flag a little too soon */
3780                 whiteFlag = blackFlag = FALSE;
3781                 continue;
3782             }
3783
3784             if (looking_at(buf, &i, "added * seconds to") ||
3785                 looking_at(buf, &i, "seconds were added to")) {
3786                 /* Update the clocks */
3787                 SendToICS(ics_prefix);
3788                 SendToICS("refresh\n");
3789                 continue;
3790             }
3791
3792             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793                 ics_clock_paused = TRUE;
3794                 StopClocks();
3795                 continue;
3796             }
3797
3798             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799                 ics_clock_paused = FALSE;
3800                 StartClocks();
3801                 continue;
3802             }
3803
3804             /* Grab player ratings from the Creating: message.
3805                Note we have to check for the special case when
3806                the ICS inserts things like [white] or [black]. */
3807             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3809                 /* star_matches:
3810                    0    player 1 name (not necessarily white)
3811                    1    player 1 rating
3812                    2    empty, white, or black (IGNORED)
3813                    3    player 2 name (not necessarily black)
3814                    4    player 2 rating
3815
3816                    The names/ratings are sorted out when the game
3817                    actually starts (below).
3818                 */
3819                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820                 player1Rating = string_to_rating(star_match[1]);
3821                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822                 player2Rating = string_to_rating(star_match[4]);
3823
3824                 if (appData.debugMode)
3825                   fprintf(debugFP,
3826                           "Ratings from 'Creating:' %s %d, %s %d\n",
3827                           player1Name, player1Rating,
3828                           player2Name, player2Rating);
3829
3830                 continue;
3831             }
3832
3833             /* Improved generic start/end-of-game messages */
3834             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836                 /* If tkind == 0: */
3837                 /* star_match[0] is the game number */
3838                 /*           [1] is the white player's name */
3839                 /*           [2] is the black player's name */
3840                 /* For end-of-game: */
3841                 /*           [3] is the reason for the game end */
3842                 /*           [4] is a PGN end game-token, preceded by " " */
3843                 /* For start-of-game: */
3844                 /*           [3] begins with "Creating" or "Continuing" */
3845                 /*           [4] is " *" or empty (don't care). */
3846                 int gamenum = atoi(star_match[0]);
3847                 char *whitename, *blackname, *why, *endtoken;
3848                 ChessMove endtype = EndOfFile;
3849
3850                 if (tkind == 0) {
3851                   whitename = star_match[1];
3852                   blackname = star_match[2];
3853                   why = star_match[3];
3854                   endtoken = star_match[4];
3855                 } else {
3856                   whitename = star_match[1];
3857                   blackname = star_match[3];
3858                   why = star_match[5];
3859                   endtoken = star_match[6];
3860                 }
3861
3862                 /* Game start messages */
3863                 if (strncmp(why, "Creating ", 9) == 0 ||
3864                     strncmp(why, "Continuing ", 11) == 0) {
3865                     gs_gamenum = gamenum;
3866                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3867                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3868 #if ZIPPY
3869                     if (appData.zippyPlay) {
3870                         ZippyGameStart(whitename, blackname);
3871                     }
3872 #endif /*ZIPPY*/
3873                     partnerBoardValid = FALSE; // [HGM] bughouse
3874                     continue;
3875                 }
3876
3877                 /* Game end messages */
3878                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3879                     ics_gamenum != gamenum) {
3880                     continue;
3881                 }
3882                 while (endtoken[0] == ' ') endtoken++;
3883                 switch (endtoken[0]) {
3884                   case '*':
3885                   default:
3886                     endtype = GameUnfinished;
3887                     break;
3888                   case '0':
3889                     endtype = BlackWins;
3890                     break;
3891                   case '1':
3892                     if (endtoken[1] == '/')
3893                       endtype = GameIsDrawn;
3894                     else
3895                       endtype = WhiteWins;
3896                     break;
3897                 }
3898                 GameEnds(endtype, why, GE_ICS);
3899 #if ZIPPY
3900                 if (appData.zippyPlay && first.initDone) {
3901                     ZippyGameEnd(endtype, why);
3902                     if (first.pr == NULL) {
3903                       /* Start the next process early so that we'll
3904                          be ready for the next challenge */
3905                       StartChessProgram(&first);
3906                     }
3907                     /* Send "new" early, in case this command takes
3908                        a long time to finish, so that we'll be ready
3909                        for the next challenge. */
3910                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3911                     Reset(TRUE, TRUE);
3912                 }
3913 #endif /*ZIPPY*/
3914                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3915                 continue;
3916             }
3917
3918             if (looking_at(buf, &i, "Removing game * from observation") ||
3919                 looking_at(buf, &i, "no longer observing game *") ||
3920                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3921                 if (gameMode == IcsObserving &&
3922                     atoi(star_match[0]) == ics_gamenum)
3923                   {
3924                       /* icsEngineAnalyze */
3925                       if (appData.icsEngineAnalyze) {
3926                             ExitAnalyzeMode();
3927                             ModeHighlight();
3928                       }
3929                       StopClocks();
3930                       gameMode = IcsIdle;
3931                       ics_gamenum = -1;
3932                       ics_user_moved = FALSE;
3933                   }
3934                 continue;
3935             }
3936
3937             if (looking_at(buf, &i, "no longer examining game *")) {
3938                 if (gameMode == IcsExamining &&
3939                     atoi(star_match[0]) == ics_gamenum)
3940                   {
3941                       gameMode = IcsIdle;
3942                       ics_gamenum = -1;
3943                       ics_user_moved = FALSE;
3944                   }
3945                 continue;
3946             }
3947
3948             /* Advance leftover_start past any newlines we find,
3949                so only partial lines can get reparsed */
3950             if (looking_at(buf, &i, "\n")) {
3951                 prevColor = curColor;
3952                 if (curColor != ColorNormal) {
3953                     if (oldi > next_out) {
3954                         SendToPlayer(&buf[next_out], oldi - next_out);
3955                         next_out = oldi;
3956                     }
3957                     Colorize(ColorNormal, FALSE);
3958                     curColor = ColorNormal;
3959                 }
3960                 if (started == STARTED_BOARD) {
3961                     started = STARTED_NONE;
3962                     parse[parse_pos] = NULLCHAR;
3963                     ParseBoard12(parse);
3964                     ics_user_moved = 0;
3965
3966                     /* Send premove here */
3967                     if (appData.premove) {
3968                       char str[MSG_SIZ];
3969                       if (currentMove == 0 &&
3970                           gameMode == IcsPlayingWhite &&
3971                           appData.premoveWhite) {
3972                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3973                         if (appData.debugMode)
3974                           fprintf(debugFP, "Sending premove:\n");
3975                         SendToICS(str);
3976                       } else if (currentMove == 1 &&
3977                                  gameMode == IcsPlayingBlack &&
3978                                  appData.premoveBlack) {
3979                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3980                         if (appData.debugMode)
3981                           fprintf(debugFP, "Sending premove:\n");
3982                         SendToICS(str);
3983                       } else if (gotPremove) {
3984                         gotPremove = 0;
3985                         ClearPremoveHighlights();
3986                         if (appData.debugMode)
3987                           fprintf(debugFP, "Sending premove:\n");
3988                           UserMoveEvent(premoveFromX, premoveFromY,
3989                                         premoveToX, premoveToY,
3990                                         premovePromoChar);
3991                       }
3992                     }
3993
3994                     /* Usually suppress following prompt */
3995                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3996                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3997                         if (looking_at(buf, &i, "*% ")) {
3998                             savingComment = FALSE;
3999                             suppressKibitz = 0;
4000                         }
4001                     }
4002                     next_out = i;
4003                 } else if (started == STARTED_HOLDINGS) {
4004                     int gamenum;
4005                     char new_piece[MSG_SIZ];
4006                     started = STARTED_NONE;
4007                     parse[parse_pos] = NULLCHAR;
4008                     if (appData.debugMode)
4009                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4010                                                         parse, currentMove);
4011                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4012                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4013                         if (gameInfo.variant == VariantNormal) {
4014                           /* [HGM] We seem to switch variant during a game!
4015                            * Presumably no holdings were displayed, so we have
4016                            * to move the position two files to the right to
4017                            * create room for them!
4018                            */
4019                           VariantClass newVariant;
4020                           switch(gameInfo.boardWidth) { // base guess on board width
4021                                 case 9:  newVariant = VariantShogi; break;
4022                                 case 10: newVariant = VariantGreat; break;
4023                                 default: newVariant = VariantCrazyhouse; break;
4024                           }
4025                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4026                           /* Get a move list just to see the header, which
4027                              will tell us whether this is really bug or zh */
4028                           if (ics_getting_history == H_FALSE) {
4029                             ics_getting_history = H_REQUESTED;
4030                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4031                             SendToICS(str);
4032                           }
4033                         }
4034                         new_piece[0] = NULLCHAR;
4035                         sscanf(parse, "game %d white [%s black [%s <- %s",
4036                                &gamenum, white_holding, black_holding,
4037                                new_piece);
4038                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4039                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4040                         /* [HGM] copy holdings to board holdings area */
4041                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4042                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4043                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4044 #if ZIPPY
4045                         if (appData.zippyPlay && first.initDone) {
4046                             ZippyHoldings(white_holding, black_holding,
4047                                           new_piece);
4048                         }
4049 #endif /*ZIPPY*/
4050                         if (tinyLayout || smallLayout) {
4051                             char wh[16], bh[16];
4052                             PackHolding(wh, white_holding);
4053                             PackHolding(bh, black_holding);
4054                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4055                                     gameInfo.white, gameInfo.black);
4056                         } else {
4057                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4058                                     gameInfo.white, white_holding,
4059                                     gameInfo.black, black_holding);
4060                         }
4061                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4062                         DrawPosition(FALSE, boards[currentMove]);
4063                         DisplayTitle(str);
4064                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4065                         sscanf(parse, "game %d white [%s black [%s <- %s",
4066                                &gamenum, white_holding, black_holding,
4067                                new_piece);
4068                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4069                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4070                         /* [HGM] copy holdings to partner-board holdings area */
4071                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4072                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4073                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4074                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4075                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4076                       }
4077                     }
4078                     /* Suppress following prompt */
4079                     if (looking_at(buf, &i, "*% ")) {
4080                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4081                         savingComment = FALSE;
4082                         suppressKibitz = 0;
4083                     }
4084                     next_out = i;
4085                 }
4086                 continue;
4087             }
4088
4089             i++;                /* skip unparsed character and loop back */
4090         }
4091
4092         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4093 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4094 //          SendToPlayer(&buf[next_out], i - next_out);
4095             started != STARTED_HOLDINGS && leftover_start > next_out) {
4096             SendToPlayer(&buf[next_out], leftover_start - next_out);
4097             next_out = i;
4098         }
4099
4100         leftover_len = buf_len - leftover_start;
4101         /* if buffer ends with something we couldn't parse,
4102            reparse it after appending the next read */
4103
4104     } else if (count == 0) {
4105         RemoveInputSource(isr);
4106         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4107     } else {
4108         DisplayFatalError(_("Error reading from ICS"), error, 1);
4109     }
4110 }
4111
4112
4113 /* Board style 12 looks like this:
4114
4115    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4116
4117  * The "<12> " is stripped before it gets to this routine.  The two
4118  * trailing 0's (flip state and clock ticking) are later addition, and
4119  * some chess servers may not have them, or may have only the first.
4120  * Additional trailing fields may be added in the future.
4121  */
4122
4123 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4124
4125 #define RELATION_OBSERVING_PLAYED    0
4126 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4127 #define RELATION_PLAYING_MYMOVE      1
4128 #define RELATION_PLAYING_NOTMYMOVE  -1
4129 #define RELATION_EXAMINING           2
4130 #define RELATION_ISOLATED_BOARD     -3
4131 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4132
4133 void
4134 ParseBoard12(string)
4135      char *string;
4136 {
4137     GameMode newGameMode;
4138     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4139     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4140     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4141     char to_play, board_chars[200];
4142     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4143     char black[32], white[32];
4144     Board board;
4145     int prevMove = currentMove;
4146     int ticking = 2;
4147     ChessMove moveType;
4148     int fromX, fromY, toX, toY;
4149     char promoChar;
4150     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4151     char *bookHit = NULL; // [HGM] book
4152     Boolean weird = FALSE, reqFlag = FALSE;
4153
4154     fromX = fromY = toX = toY = -1;
4155
4156     newGame = FALSE;
4157
4158     if (appData.debugMode)
4159       fprintf(debugFP, _("Parsing board: %s\n"), string);
4160
4161     move_str[0] = NULLCHAR;
4162     elapsed_time[0] = NULLCHAR;
4163     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4164         int  i = 0, j;
4165         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4166             if(string[i] == ' ') { ranks++; files = 0; }
4167             else files++;
4168             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4169             i++;
4170         }
4171         for(j = 0; j <i; j++) board_chars[j] = string[j];
4172         board_chars[i] = '\0';
4173         string += i + 1;
4174     }
4175     n = sscanf(string, PATTERN, &to_play, &double_push,
4176                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4177                &gamenum, white, black, &relation, &basetime, &increment,
4178                &white_stren, &black_stren, &white_time, &black_time,
4179                &moveNum, str, elapsed_time, move_str, &ics_flip,
4180                &ticking);
4181
4182     if (n < 21) {
4183         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4184         DisplayError(str, 0);
4185         return;
4186     }
4187
4188     /* Convert the move number to internal form */
4189     moveNum = (moveNum - 1) * 2;
4190     if (to_play == 'B') moveNum++;
4191     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4192       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4193                         0, 1);
4194       return;
4195     }
4196
4197     switch (relation) {
4198       case RELATION_OBSERVING_PLAYED:
4199       case RELATION_OBSERVING_STATIC:
4200         if (gamenum == -1) {
4201             /* Old ICC buglet */
4202             relation = RELATION_OBSERVING_STATIC;
4203         }
4204         newGameMode = IcsObserving;
4205         break;
4206       case RELATION_PLAYING_MYMOVE:
4207       case RELATION_PLAYING_NOTMYMOVE:
4208         newGameMode =
4209           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4210             IcsPlayingWhite : IcsPlayingBlack;
4211         break;
4212       case RELATION_EXAMINING:
4213         newGameMode = IcsExamining;
4214         break;
4215       case RELATION_ISOLATED_BOARD:
4216       default:
4217         /* Just display this board.  If user was doing something else,
4218            we will forget about it until the next board comes. */
4219         newGameMode = IcsIdle;
4220         break;
4221       case RELATION_STARTING_POSITION:
4222         newGameMode = gameMode;
4223         break;
4224     }
4225
4226     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4227          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4228       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4229       char *toSqr;
4230       for (k = 0; k < ranks; k++) {
4231         for (j = 0; j < files; j++)
4232           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4233         if(gameInfo.holdingsWidth > 1) {
4234              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4235              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4236         }
4237       }
4238       CopyBoard(partnerBoard, board);
4239       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4240         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4241         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4243       if(toSqr = strchr(str, '-')) {
4244         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4245         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4246       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4247       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4248       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4249       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4250       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4251       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4252                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4253       DisplayMessage(partnerStatus, "");
4254         partnerBoardValid = TRUE;
4255       return;
4256     }
4257
4258     /* Modify behavior for initial board display on move listing
4259        of wild games.
4260        */
4261     switch (ics_getting_history) {
4262       case H_FALSE:
4263       case H_REQUESTED:
4264         break;
4265       case H_GOT_REQ_HEADER:
4266       case H_GOT_UNREQ_HEADER:
4267         /* This is the initial position of the current game */
4268         gamenum = ics_gamenum;
4269         moveNum = 0;            /* old ICS bug workaround */
4270         if (to_play == 'B') {
4271           startedFromSetupPosition = TRUE;
4272           blackPlaysFirst = TRUE;
4273           moveNum = 1;
4274           if (forwardMostMove == 0) forwardMostMove = 1;
4275           if (backwardMostMove == 0) backwardMostMove = 1;
4276           if (currentMove == 0) currentMove = 1;
4277         }
4278         newGameMode = gameMode;
4279         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4280         break;
4281       case H_GOT_UNWANTED_HEADER:
4282         /* This is an initial board that we don't want */
4283         return;
4284       case H_GETTING_MOVES:
4285         /* Should not happen */
4286         DisplayError(_("Error gathering move list: extra board"), 0);
4287         ics_getting_history = H_FALSE;
4288         return;
4289     }
4290
4291    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4292                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4293      /* [HGM] We seem to have switched variant unexpectedly
4294       * Try to guess new variant from board size
4295       */
4296           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4297           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4298           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4299           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4300           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4301           if(!weird) newVariant = VariantNormal;
4302           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4303           /* Get a move list just to see the header, which
4304              will tell us whether this is really bug or zh */
4305           if (ics_getting_history == H_FALSE) {
4306             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309           }
4310     }
4311
4312     /* Take action if this is the first board of a new game, or of a
4313        different game than is currently being displayed.  */
4314     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4315         relation == RELATION_ISOLATED_BOARD) {
4316
4317         /* Forget the old game and get the history (if any) of the new one */
4318         if (gameMode != BeginningOfGame) {
4319           Reset(TRUE, TRUE);
4320         }
4321         newGame = TRUE;
4322         if (appData.autoRaiseBoard) BoardToTop();
4323         prevMove = -3;
4324         if (gamenum == -1) {
4325             newGameMode = IcsIdle;
4326         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4327                    appData.getMoveList && !reqFlag) {
4328             /* Need to get game history */
4329             ics_getting_history = H_REQUESTED;
4330             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4331             SendToICS(str);
4332         }
4333
4334         /* Initially flip the board to have black on the bottom if playing
4335            black or if the ICS flip flag is set, but let the user change
4336            it with the Flip View button. */
4337         flipView = appData.autoFlipView ?
4338           (newGameMode == IcsPlayingBlack) || ics_flip :
4339           appData.flipView;
4340
4341         /* Done with values from previous mode; copy in new ones */
4342         gameMode = newGameMode;
4343         ModeHighlight();
4344         ics_gamenum = gamenum;
4345         if (gamenum == gs_gamenum) {
4346             int klen = strlen(gs_kind);
4347             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4348             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4349             gameInfo.event = StrSave(str);
4350         } else {
4351             gameInfo.event = StrSave("ICS game");
4352         }
4353         gameInfo.site = StrSave(appData.icsHost);
4354         gameInfo.date = PGNDate();
4355         gameInfo.round = StrSave("-");
4356         gameInfo.white = StrSave(white);
4357         gameInfo.black = StrSave(black);
4358         timeControl = basetime * 60 * 1000;
4359         timeControl_2 = 0;
4360         timeIncrement = increment * 1000;
4361         movesPerSession = 0;
4362         gameInfo.timeControl = TimeControlTagValue();
4363         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4364   if (appData.debugMode) {
4365     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4366     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4367     setbuf(debugFP, NULL);
4368   }
4369
4370         gameInfo.outOfBook = NULL;
4371
4372         /* Do we have the ratings? */
4373         if (strcmp(player1Name, white) == 0 &&
4374             strcmp(player2Name, black) == 0) {
4375             if (appData.debugMode)
4376               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4377                       player1Rating, player2Rating);
4378             gameInfo.whiteRating = player1Rating;
4379             gameInfo.blackRating = player2Rating;
4380         } else if (strcmp(player2Name, white) == 0 &&
4381                    strcmp(player1Name, black) == 0) {
4382             if (appData.debugMode)
4383               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4384                       player2Rating, player1Rating);
4385             gameInfo.whiteRating = player2Rating;
4386             gameInfo.blackRating = player1Rating;
4387         }
4388         player1Name[0] = player2Name[0] = NULLCHAR;
4389
4390         /* Silence shouts if requested */
4391         if (appData.quietPlay &&
4392             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4393             SendToICS(ics_prefix);
4394             SendToICS("set shout 0\n");
4395         }
4396     }
4397
4398     /* Deal with midgame name changes */
4399     if (!newGame) {
4400         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4401             if (gameInfo.white) free(gameInfo.white);
4402             gameInfo.white = StrSave(white);
4403         }
4404         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4405             if (gameInfo.black) free(gameInfo.black);
4406             gameInfo.black = StrSave(black);
4407         }
4408     }
4409
4410     /* Throw away game result if anything actually changes in examine mode */
4411     if (gameMode == IcsExamining && !newGame) {
4412         gameInfo.result = GameUnfinished;
4413         if (gameInfo.resultDetails != NULL) {
4414             free(gameInfo.resultDetails);
4415             gameInfo.resultDetails = NULL;
4416         }
4417     }
4418
4419     /* In pausing && IcsExamining mode, we ignore boards coming
4420        in if they are in a different variation than we are. */
4421     if (pauseExamInvalid) return;
4422     if (pausing && gameMode == IcsExamining) {
4423         if (moveNum <= pauseExamForwardMostMove) {
4424             pauseExamInvalid = TRUE;
4425             forwardMostMove = pauseExamForwardMostMove;
4426             return;
4427         }
4428     }
4429
4430   if (appData.debugMode) {
4431     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4432   }
4433     /* Parse the board */
4434     for (k = 0; k < ranks; k++) {
4435       for (j = 0; j < files; j++)
4436         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4437       if(gameInfo.holdingsWidth > 1) {
4438            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4439            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4440       }
4441     }
4442     CopyBoard(boards[moveNum], board);
4443     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4444     if (moveNum == 0) {
4445         startedFromSetupPosition =
4446           !CompareBoards(board, initialPosition);
4447         if(startedFromSetupPosition)
4448             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4449     }
4450
4451     /* [HGM] Set castling rights. Take the outermost Rooks,
4452        to make it also work for FRC opening positions. Note that board12
4453        is really defective for later FRC positions, as it has no way to
4454        indicate which Rook can castle if they are on the same side of King.
4455        For the initial position we grant rights to the outermost Rooks,
4456        and remember thos rights, and we then copy them on positions
4457        later in an FRC game. This means WB might not recognize castlings with
4458        Rooks that have moved back to their original position as illegal,
4459        but in ICS mode that is not its job anyway.
4460     */
4461     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4462     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4463
4464         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4465             if(board[0][i] == WhiteRook) j = i;
4466         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4467         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4471             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4472         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476
4477         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4478         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4479             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4480         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4481             if(board[BOARD_HEIGHT-1][k] == bKing)
4482                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4483         if(gameInfo.variant == VariantTwoKings) {
4484             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4485             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4486             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4487         }
4488     } else { int r;
4489         r = boards[moveNum][CASTLING][0] = initialRights[0];
4490         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4491         r = boards[moveNum][CASTLING][1] = initialRights[1];
4492         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4493         r = boards[moveNum][CASTLING][3] = initialRights[3];
4494         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4495         r = boards[moveNum][CASTLING][4] = initialRights[4];
4496         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4497         /* wildcastle kludge: always assume King has rights */
4498         r = boards[moveNum][CASTLING][2] = initialRights[2];
4499         r = boards[moveNum][CASTLING][5] = initialRights[5];
4500     }
4501     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4502     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4503
4504
4505     if (ics_getting_history == H_GOT_REQ_HEADER ||
4506         ics_getting_history == H_GOT_UNREQ_HEADER) {
4507         /* This was an initial position from a move list, not
4508            the current position */
4509         return;
4510     }
4511
4512     /* Update currentMove and known move number limits */
4513     newMove = newGame || moveNum > forwardMostMove;
4514
4515     if (newGame) {
4516         forwardMostMove = backwardMostMove = currentMove = moveNum;
4517         if (gameMode == IcsExamining && moveNum == 0) {
4518           /* Workaround for ICS limitation: we are not told the wild
4519              type when starting to examine a game.  But if we ask for
4520              the move list, the move list header will tell us */
4521             ics_getting_history = H_REQUESTED;
4522             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4523             SendToICS(str);
4524         }
4525     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4526                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4527 #if ZIPPY
4528         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4529         /* [HGM] applied this also to an engine that is silently watching        */
4530         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4531             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4532             gameInfo.variant == currentlyInitializedVariant) {
4533           takeback = forwardMostMove - moveNum;
4534           for (i = 0; i < takeback; i++) {
4535             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4536             SendToProgram("undo\n", &first);
4537           }
4538         }
4539 #endif
4540
4541         forwardMostMove = moveNum;
4542         if (!pausing || currentMove > forwardMostMove)
4543           currentMove = forwardMostMove;
4544     } else {
4545         /* New part of history that is not contiguous with old part */
4546         if (pausing && gameMode == IcsExamining) {
4547             pauseExamInvalid = TRUE;
4548             forwardMostMove = pauseExamForwardMostMove;
4549             return;
4550         }
4551         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4552 #if ZIPPY
4553             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4554                 // [HGM] when we will receive the move list we now request, it will be
4555                 // fed to the engine from the first move on. So if the engine is not
4556                 // in the initial position now, bring it there.
4557                 InitChessProgram(&first, 0);
4558             }
4559 #endif
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564         forwardMostMove = backwardMostMove = currentMove = moveNum;
4565     }
4566
4567     /* Update the clocks */
4568     if (strchr(elapsed_time, '.')) {
4569       /* Time is in ms */
4570       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4571       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4572     } else {
4573       /* Time is in seconds */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4576     }
4577
4578
4579 #if ZIPPY
4580     if (appData.zippyPlay && newGame &&
4581         gameMode != IcsObserving && gameMode != IcsIdle &&
4582         gameMode != IcsExamining)
4583       ZippyFirstBoard(moveNum, basetime, increment);
4584 #endif
4585
4586     /* Put the move on the move list, first converting
4587        to canonical algebraic form. */
4588     if (moveNum > 0) {
4589   if (appData.debugMode) {
4590     if (appData.debugMode) { int f = forwardMostMove;
4591         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4592                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4593                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4594     }
4595     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4596     fprintf(debugFP, "moveNum = %d\n", moveNum);
4597     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4598     setbuf(debugFP, NULL);
4599   }
4600         if (moveNum <= backwardMostMove) {
4601             /* We don't know what the board looked like before
4602                this move.  Punt. */
4603           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4604             strcat(parseList[moveNum - 1], " ");
4605             strcat(parseList[moveNum - 1], elapsed_time);
4606             moveList[moveNum - 1][0] = NULLCHAR;
4607         } else if (strcmp(move_str, "none") == 0) {
4608             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4609             /* Again, we don't know what the board looked like;
4610                this is really the start of the game. */
4611             parseList[moveNum - 1][0] = NULLCHAR;
4612             moveList[moveNum - 1][0] = NULLCHAR;
4613             backwardMostMove = moveNum;
4614             startedFromSetupPosition = TRUE;
4615             fromX = fromY = toX = toY = -1;
4616         } else {
4617           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4618           //                 So we parse the long-algebraic move string in stead of the SAN move
4619           int valid; char buf[MSG_SIZ], *prom;
4620
4621           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4622                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4623           // str looks something like "Q/a1-a2"; kill the slash
4624           if(str[1] == '/')
4625             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4626           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4627           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4628                 strcat(buf, prom); // long move lacks promo specification!
4629           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4630                 if(appData.debugMode)
4631                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4632                 safeStrCpy(move_str, buf, MSG_SIZ);
4633           }
4634           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4635                                 &fromX, &fromY, &toX, &toY, &promoChar)
4636                || ParseOneMove(buf, moveNum - 1, &moveType,
4637                                 &fromX, &fromY, &toX, &toY, &promoChar);
4638           // end of long SAN patch
4639           if (valid) {
4640             (void) CoordsToAlgebraic(boards[moveNum - 1],
4641                                      PosFlags(moveNum - 1),
4642                                      fromY, fromX, toY, toX, promoChar,
4643                                      parseList[moveNum-1]);
4644             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4645               case MT_NONE:
4646               case MT_STALEMATE:
4647               default:
4648                 break;
4649               case MT_CHECK:
4650                 if(gameInfo.variant != VariantShogi)
4651                     strcat(parseList[moveNum - 1], "+");
4652                 break;
4653               case MT_CHECKMATE:
4654               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4655                 strcat(parseList[moveNum - 1], "#");
4656                 break;
4657             }
4658             strcat(parseList[moveNum - 1], " ");
4659             strcat(parseList[moveNum - 1], elapsed_time);
4660             /* currentMoveString is set as a side-effect of ParseOneMove */
4661             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4662             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4663             strcat(moveList[moveNum - 1], "\n");
4664
4665             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4666                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4667               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4668                 ChessSquare old, new = boards[moveNum][k][j];
4669                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4670                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4671                   if(old == new) continue;
4672                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4673                   else if(new == WhiteWazir || new == BlackWazir) {
4674                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4675                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4676                       else boards[moveNum][k][j] = old; // preserve type of Gold
4677                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4678                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4679               }
4680           } else {
4681             /* Move from ICS was illegal!?  Punt. */
4682             if (appData.debugMode) {
4683               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4684               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4685             }
4686             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4687             strcat(parseList[moveNum - 1], " ");
4688             strcat(parseList[moveNum - 1], elapsed_time);
4689             moveList[moveNum - 1][0] = NULLCHAR;
4690             fromX = fromY = toX = toY = -1;
4691           }
4692         }
4693   if (appData.debugMode) {
4694     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4695     setbuf(debugFP, NULL);
4696   }
4697
4698 #if ZIPPY
4699         /* Send move to chess program (BEFORE animating it). */
4700         if (appData.zippyPlay && !newGame && newMove &&
4701            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4702
4703             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4704                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4705                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4707                             move_str);
4708                     DisplayError(str, 0);
4709                 } else {
4710                     if (first.sendTime) {
4711                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4712                     }
4713                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4714                     if (firstMove && !bookHit) {
4715                         firstMove = FALSE;
4716                         if (first.useColors) {
4717                           SendToProgram(gameMode == IcsPlayingWhite ?
4718                                         "white\ngo\n" :
4719                                         "black\ngo\n", &first);
4720                         } else {
4721                           SendToProgram("go\n", &first);
4722                         }
4723                         first.maybeThinking = TRUE;
4724                     }
4725                 }
4726             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4727               if (moveList[moveNum - 1][0] == NULLCHAR) {
4728                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4729                 DisplayError(str, 0);
4730               } else {
4731                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4732                 SendMoveToProgram(moveNum - 1, &first);
4733               }
4734             }
4735         }
4736 #endif
4737     }
4738
4739     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4740         /* If move comes from a remote source, animate it.  If it
4741            isn't remote, it will have already been animated. */
4742         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4743             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4744         }
4745         if (!pausing && appData.highlightLastMove) {
4746             SetHighlights(fromX, fromY, toX, toY);
4747         }
4748     }
4749
4750     /* Start the clocks */
4751     whiteFlag = blackFlag = FALSE;
4752     appData.clockMode = !(basetime == 0 && increment == 0);
4753     if (ticking == 0) {
4754       ics_clock_paused = TRUE;
4755       StopClocks();
4756     } else if (ticking == 1) {
4757       ics_clock_paused = FALSE;
4758     }
4759     if (gameMode == IcsIdle ||
4760         relation == RELATION_OBSERVING_STATIC ||
4761         relation == RELATION_EXAMINING ||
4762         ics_clock_paused)
4763       DisplayBothClocks();
4764     else
4765       StartClocks();
4766
4767     /* Display opponents and material strengths */
4768     if (gameInfo.variant != VariantBughouse &&
4769         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4770         if (tinyLayout || smallLayout) {
4771             if(gameInfo.variant == VariantNormal)
4772               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4773                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4774                     basetime, increment);
4775             else
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment, (int) gameInfo.variant);
4779         } else {
4780             if(gameInfo.variant == VariantNormal)
4781               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4782                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4783                     basetime, increment);
4784             else
4785               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4786                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4787                     basetime, increment, VariantName(gameInfo.variant));
4788         }
4789         DisplayTitle(str);
4790   if (appData.debugMode) {
4791     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4792   }
4793     }
4794
4795
4796     /* Display the board */
4797     if (!pausing && !appData.noGUI) {
4798
4799       if (appData.premove)
4800           if (!gotPremove ||
4801              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4802              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4803               ClearPremoveHighlights();
4804
4805       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4806         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4807       DrawPosition(j, boards[currentMove]);
4808
4809       DisplayMove(moveNum - 1);
4810       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4811             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4812               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4813         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4814       }
4815     }
4816
4817     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4818 #if ZIPPY
4819     if(bookHit) { // [HGM] book: simulate book reply
4820         static char bookMove[MSG_SIZ]; // a bit generous?
4821
4822         programStats.nodes = programStats.depth = programStats.time =
4823         programStats.score = programStats.got_only_move = 0;
4824         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4825
4826         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4827         strcat(bookMove, bookHit);
4828         HandleMachineMove(bookMove, &first);
4829     }
4830 #endif
4831 }
4832
4833 void
4834 GetMoveListEvent()
4835 {
4836     char buf[MSG_SIZ];
4837     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4838         ics_getting_history = H_REQUESTED;
4839         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4840         SendToICS(buf);
4841     }
4842 }
4843
4844 void
4845 AnalysisPeriodicEvent(force)
4846      int force;
4847 {
4848     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4849          && !force) || !appData.periodicUpdates)
4850       return;
4851
4852     /* Send . command to Crafty to collect stats */
4853     SendToProgram(".\n", &first);
4854
4855     /* Don't send another until we get a response (this makes
4856        us stop sending to old Crafty's which don't understand
4857        the "." command (sending illegal cmds resets node count & time,
4858        which looks bad)) */
4859     programStats.ok_to_send = 0;
4860 }
4861
4862 void ics_update_width(new_width)
4863         int new_width;
4864 {
4865         ics_printf("set width %d\n", new_width);
4866 }
4867
4868 void
4869 SendMoveToProgram(moveNum, cps)
4870      int moveNum;
4871      ChessProgramState *cps;
4872 {
4873     char buf[MSG_SIZ];
4874
4875     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4876         // null move in variant where engine does not understand it (for analysis purposes)
4877         SendBoard(cps, moveNum + 1); // send position after move in stead.
4878         return;
4879     }
4880     if (cps->useUsermove) {
4881       SendToProgram("usermove ", cps);
4882     }
4883     if (cps->useSAN) {
4884       char *space;
4885       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4886         int len = space - parseList[moveNum];
4887         memcpy(buf, parseList[moveNum], len);
4888         buf[len++] = '\n';
4889         buf[len] = NULLCHAR;
4890       } else {
4891         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4892       }
4893       SendToProgram(buf, cps);
4894     } else {
4895       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4896         AlphaRank(moveList[moveNum], 4);
4897         SendToProgram(moveList[moveNum], cps);
4898         AlphaRank(moveList[moveNum], 4); // and back
4899       } else
4900       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4901        * the engine. It would be nice to have a better way to identify castle
4902        * moves here. */
4903       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4904                                                                          && cps->useOOCastle) {
4905         int fromX = moveList[moveNum][0] - AAA;
4906         int fromY = moveList[moveNum][1] - ONE;
4907         int toX = moveList[moveNum][2] - AAA;
4908         int toY = moveList[moveNum][3] - ONE;
4909         if((boards[moveNum][fromY][fromX] == WhiteKing
4910             && boards[moveNum][toY][toX] == WhiteRook)
4911            || (boards[moveNum][fromY][fromX] == BlackKing
4912                && boards[moveNum][toY][toX] == BlackRook)) {
4913           if(toX > fromX) SendToProgram("O-O\n", cps);
4914           else SendToProgram("O-O-O\n", cps);
4915         }
4916         else SendToProgram(moveList[moveNum], cps);
4917       } else
4918       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4919         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4920           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4921           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4922                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4923         } else
4924           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4925                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4926         SendToProgram(buf, cps);
4927       }
4928       else SendToProgram(moveList[moveNum], cps);
4929       /* End of additions by Tord */
4930     }
4931
4932     /* [HGM] setting up the opening has brought engine in force mode! */
4933     /*       Send 'go' if we are in a mode where machine should play. */
4934     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4935         (gameMode == TwoMachinesPlay   ||
4936 #if ZIPPY
4937          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4938 #endif
4939          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4940         SendToProgram("go\n", cps);
4941   if (appData.debugMode) {
4942     fprintf(debugFP, "(extra)\n");
4943   }
4944     }
4945     setboardSpoiledMachineBlack = 0;
4946 }
4947
4948 void
4949 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4950      ChessMove moveType;
4951      int fromX, fromY, toX, toY;
4952      char promoChar;
4953 {
4954     char user_move[MSG_SIZ];
4955
4956     switch (moveType) {
4957       default:
4958         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4959                 (int)moveType, fromX, fromY, toX, toY);
4960         DisplayError(user_move + strlen("say "), 0);
4961         break;
4962       case WhiteKingSideCastle:
4963       case BlackKingSideCastle:
4964       case WhiteQueenSideCastleWild:
4965       case BlackQueenSideCastleWild:
4966       /* PUSH Fabien */
4967       case WhiteHSideCastleFR:
4968       case BlackHSideCastleFR:
4969       /* POP Fabien */
4970         snprintf(user_move, MSG_SIZ, "o-o\n");
4971         break;
4972       case WhiteQueenSideCastle:
4973       case BlackQueenSideCastle:
4974       case WhiteKingSideCastleWild:
4975       case BlackKingSideCastleWild:
4976       /* PUSH Fabien */
4977       case WhiteASideCastleFR:
4978       case BlackASideCastleFR:
4979       /* POP Fabien */
4980         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4981         break;
4982       case WhiteNonPromotion:
4983       case BlackNonPromotion:
4984         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4985         break;
4986       case WhitePromotion:
4987       case BlackPromotion:
4988         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4989           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4990                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4991                 PieceToChar(WhiteFerz));
4992         else if(gameInfo.variant == VariantGreat)
4993           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteMan));
4996         else
4997           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 promoChar);
5000         break;
5001       case WhiteDrop:
5002       case BlackDrop:
5003       drop:
5004         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5005                  ToUpper(PieceToChar((ChessSquare) fromX)),
5006                  AAA + toX, ONE + toY);
5007         break;
5008       case IllegalMove:  /* could be a variant we don't quite understand */
5009         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5010       case NormalMove:
5011       case WhiteCapturesEnPassant:
5012       case BlackCapturesEnPassant:
5013         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5014                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5015         break;
5016     }
5017     SendToICS(user_move);
5018     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5019         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5020 }
5021
5022 void
5023 UploadGameEvent()
5024 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5025     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5026     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5027     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5028         DisplayError("You cannot do this while you are playing or observing", 0);
5029         return;
5030     }
5031     if(gameMode != IcsExamining) { // is this ever not the case?
5032         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5033
5034         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5035           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5036         } else { // on FICS we must first go to general examine mode
5037           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5038         }
5039         if(gameInfo.variant != VariantNormal) {
5040             // try figure out wild number, as xboard names are not always valid on ICS
5041             for(i=1; i<=36; i++) {
5042               snprintf(buf, MSG_SIZ, "wild/%d", i);
5043                 if(StringToVariant(buf) == gameInfo.variant) break;
5044             }
5045             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5046             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5047             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5048         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5049         SendToICS(ics_prefix);
5050         SendToICS(buf);
5051         if(startedFromSetupPosition || backwardMostMove != 0) {
5052           fen = PositionToFEN(backwardMostMove, NULL);
5053           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5054             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5055             SendToICS(buf);
5056           } else { // FICS: everything has to set by separate bsetup commands
5057             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5058             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5059             SendToICS(buf);
5060             if(!WhiteOnMove(backwardMostMove)) {
5061                 SendToICS("bsetup tomove black\n");
5062             }
5063             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5064             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5065             SendToICS(buf);
5066             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5067             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5068             SendToICS(buf);
5069             i = boards[backwardMostMove][EP_STATUS];
5070             if(i >= 0) { // set e.p.
5071               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5072                 SendToICS(buf);
5073             }
5074             bsetup++;
5075           }
5076         }
5077       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5078             SendToICS("bsetup done\n"); // switch to normal examining.
5079     }
5080     for(i = backwardMostMove; i<last; i++) {
5081         char buf[20];
5082         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5083         SendToICS(buf);
5084     }
5085     SendToICS(ics_prefix);
5086     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5087 }
5088
5089 void
5090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5091      int rf, ff, rt, ft;
5092      char promoChar;
5093      char move[7];
5094 {
5095     if (rf == DROP_RANK) {
5096       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5097       sprintf(move, "%c@%c%c\n",
5098                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5099     } else {
5100         if (promoChar == 'x' || promoChar == NULLCHAR) {
5101           sprintf(move, "%c%c%c%c\n",
5102                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5103         } else {
5104             sprintf(move, "%c%c%c%c%c\n",
5105                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5106         }
5107     }
5108 }
5109
5110 void
5111 ProcessICSInitScript(f)
5112      FILE *f;
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep(int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = 1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int PromoScroll(int x, int y)
5149 {
5150   int step = 0;
5151
5152   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5153   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5154   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5155   if(!step) return FALSE;
5156   lastX = x; lastY = y;
5157   if((promoSweep < BlackPawn) == flipView) step = -step;
5158   if(step > 0) selectFlag = 1;
5159   if(!selectFlag) Sweep(step);
5160   return FALSE;
5161 }
5162
5163 void
5164 NextPiece(int step)
5165 {
5166     ChessSquare piece = boards[currentMove][toY][toX];
5167     do {
5168         pieceSweep -= step;
5169         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5170         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5171         if(!step) step = -1;
5172     } while(PieceToChar(pieceSweep) == '.');
5173     boards[currentMove][toY][toX] = pieceSweep;
5174     DrawPosition(FALSE, boards[currentMove]);
5175     boards[currentMove][toY][toX] = piece;
5176 }
5177 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5178 void
5179 AlphaRank(char *move, int n)
5180 {
5181 //    char *p = move, c; int x, y;
5182
5183     if (appData.debugMode) {
5184         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5185     }
5186
5187     if(move[1]=='*' &&
5188        move[2]>='0' && move[2]<='9' &&
5189        move[3]>='a' && move[3]<='x'    ) {
5190         move[1] = '@';
5191         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5192         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5193     } else
5194     if(move[0]>='0' && move[0]<='9' &&
5195        move[1]>='a' && move[1]<='x' &&
5196        move[2]>='0' && move[2]<='9' &&
5197        move[3]>='a' && move[3]<='x'    ) {
5198         /* input move, Shogi -> normal */
5199         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5200         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5201         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5202         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5203     } else
5204     if(move[1]=='@' &&
5205        move[3]>='0' && move[3]<='9' &&
5206        move[2]>='a' && move[2]<='x'    ) {
5207         move[1] = '*';
5208         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5209         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5210     } else
5211     if(
5212        move[0]>='a' && move[0]<='x' &&
5213        move[3]>='0' && move[3]<='9' &&
5214        move[2]>='a' && move[2]<='x'    ) {
5215          /* output move, normal -> Shogi */
5216         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5217         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5218         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5219         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5220         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5221     }
5222     if (appData.debugMode) {
5223         fprintf(debugFP, "   out = '%s'\n", move);
5224     }
5225 }
5226
5227 char yy_textstr[8000];
5228
5229 /* Parser for moves from gnuchess, ICS, or user typein box */
5230 Boolean
5231 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5232      char *move;
5233      int moveNum;
5234      ChessMove *moveType;
5235      int *fromX, *fromY, *toX, *toY;
5236      char *promoChar;
5237 {
5238     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5239
5240     switch (*moveType) {
5241       case WhitePromotion:
5242       case BlackPromotion:
5243       case WhiteNonPromotion:
5244       case BlackNonPromotion:
5245       case NormalMove:
5246       case WhiteCapturesEnPassant:
5247       case BlackCapturesEnPassant:
5248       case WhiteKingSideCastle:
5249       case WhiteQueenSideCastle:
5250       case BlackKingSideCastle:
5251       case BlackQueenSideCastle:
5252       case WhiteKingSideCastleWild:
5253       case WhiteQueenSideCastleWild:
5254       case BlackKingSideCastleWild:
5255       case BlackQueenSideCastleWild:
5256       /* Code added by Tord: */
5257       case WhiteHSideCastleFR:
5258       case WhiteASideCastleFR:
5259       case BlackHSideCastleFR:
5260       case BlackASideCastleFR:
5261       /* End of code added by Tord */
5262       case IllegalMove:         /* bug or odd chess variant */
5263         *fromX = currentMoveString[0] - AAA;
5264         *fromY = currentMoveString[1] - ONE;
5265         *toX = currentMoveString[2] - AAA;
5266         *toY = currentMoveString[3] - ONE;
5267         *promoChar = currentMoveString[4];
5268         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5269             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5270     if (appData.debugMode) {
5271         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5272     }
5273             *fromX = *fromY = *toX = *toY = 0;
5274             return FALSE;
5275         }
5276         if (appData.testLegality) {
5277           return (*moveType != IllegalMove);
5278         } else {
5279           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5280                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5281         }
5282
5283       case WhiteDrop:
5284       case BlackDrop:
5285         *fromX = *moveType == WhiteDrop ?
5286           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5287           (int) CharToPiece(ToLower(currentMoveString[0]));
5288         *fromY = DROP_RANK;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = NULLCHAR;
5292         return TRUE;
5293
5294       case AmbiguousMove:
5295       case ImpossibleMove:
5296       case EndOfFile:
5297       case ElapsedTime:
5298       case Comment:
5299       case PGNTag:
5300       case NAG:
5301       case WhiteWins:
5302       case BlackWins:
5303       case GameIsDrawn:
5304       default:
5305     if (appData.debugMode) {
5306         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5307     }
5308         /* bug? */
5309         *fromX = *fromY = *toX = *toY = 0;
5310         *promoChar = NULLCHAR;
5311         return FALSE;
5312     }
5313 }
5314
5315 Boolean pushed = FALSE;
5316 char *lastParseAttempt;
5317
5318 void
5319 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5320 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5321   int fromX, fromY, toX, toY; char promoChar;
5322   ChessMove moveType;
5323   Boolean valid;
5324   int nr = 0;
5325
5326   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5327     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5328     pushed = TRUE;
5329   }
5330   endPV = forwardMostMove;
5331   do {
5332     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5333     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5334     lastParseAttempt = pv;
5335     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5336 if(appData.debugMode){
5337 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5338 }
5339     if(!valid && nr == 0 &&
5340        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5341         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5342         // Hande case where played move is different from leading PV move
5343         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5344         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5345         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5346         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5347           endPV += 2; // if position different, keep this
5348           moveList[endPV-1][0] = fromX + AAA;
5349           moveList[endPV-1][1] = fromY + ONE;
5350           moveList[endPV-1][2] = toX + AAA;
5351           moveList[endPV-1][3] = toY + ONE;
5352           parseList[endPV-1][0] = NULLCHAR;
5353           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5354         }
5355       }
5356     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5357     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5358     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5359     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5360         valid++; // allow comments in PV
5361         continue;
5362     }
5363     nr++;
5364     if(endPV+1 > framePtr) break; // no space, truncate
5365     if(!valid) break;
5366     endPV++;
5367     CopyBoard(boards[endPV], boards[endPV-1]);
5368     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5369     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5370     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5371     CoordsToAlgebraic(boards[endPV - 1],
5372                              PosFlags(endPV - 1),
5373                              fromY, fromX, toY, toX, promoChar,
5374                              parseList[endPV - 1]);
5375   } while(valid);
5376   if(atEnd == 2) return; // used hidden, for PV conversion
5377   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5378   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5379   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5380                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5381   DrawPosition(TRUE, boards[currentMove]);
5382 }
5383
5384 int
5385 MultiPV(ChessProgramState *cps)
5386 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5387         int i;
5388         for(i=0; i<cps->nrOptions; i++)
5389             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5390                 return i;
5391         return -1;
5392 }
5393
5394 Boolean
5395 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5396 {
5397         int startPV, multi, lineStart, origIndex = index;
5398         char *p, buf2[MSG_SIZ];
5399
5400         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5401         lastX = x; lastY = y;
5402         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5403         lineStart = startPV = index;
5404         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5405         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5406         index = startPV;
5407         do{ while(buf[index] && buf[index] != '\n') index++;
5408         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5409         buf[index] = 0;
5410         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5411                 int n = first.option[multi].value;
5412                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5413                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5414                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5415                 first.option[multi].value = n;
5416                 *start = *end = 0;
5417                 return FALSE;
5418         }
5419         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5420         *start = startPV; *end = index-1;
5421         return TRUE;
5422 }
5423
5424 char *
5425 PvToSAN(char *pv)
5426 {
5427         static char buf[10*MSG_SIZ];
5428         int i, k=0, savedEnd=endPV;
5429         *buf = NULLCHAR;
5430         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5431         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5432         for(i = forwardMostMove; i<endPV; i++){
5433             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5434             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5435             k += strlen(buf+k);
5436         }
5437         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5438         if(forwardMostMove < savedEnd) PopInner(0);
5439         endPV = savedEnd;
5440         return buf;
5441 }
5442
5443 Boolean
5444 LoadPV(int x, int y)
5445 { // called on right mouse click to load PV
5446   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5447   lastX = x; lastY = y;
5448   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5449   return TRUE;
5450 }
5451
5452 void
5453 UnLoadPV()
5454 {
5455   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5456   if(endPV < 0) return;
5457   endPV = -1;
5458   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5459         Boolean saveAnimate = appData.animate;
5460         if(pushed) {
5461             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5462                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5463             } else storedGames--; // abandon shelved tail of original game
5464         }
5465         pushed = FALSE;
5466         forwardMostMove = currentMove;
5467         currentMove = oldFMM;
5468         appData.animate = FALSE;
5469         ToNrEvent(forwardMostMove);
5470         appData.animate = saveAnimate;
5471   }
5472   currentMove = forwardMostMove;
5473   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5474   ClearPremoveHighlights();
5475   DrawPosition(TRUE, boards[currentMove]);
5476 }
5477
5478 void
5479 MovePV(int x, int y, int h)
5480 { // step through PV based on mouse coordinates (called on mouse move)
5481   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5482
5483   // we must somehow check if right button is still down (might be released off board!)
5484   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5485   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5486   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5487   if(!step) return;
5488   lastX = x; lastY = y;
5489
5490   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5491   if(endPV < 0) return;
5492   if(y < margin) step = 1; else
5493   if(y > h - margin) step = -1;
5494   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5495   currentMove += step;
5496   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5497   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5498                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5499   DrawPosition(FALSE, boards[currentMove]);
5500 }
5501
5502
5503 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5504 // All positions will have equal probability, but the current method will not provide a unique
5505 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5506 #define DARK 1
5507 #define LITE 2
5508 #define ANY 3
5509
5510 int squaresLeft[4];
5511 int piecesLeft[(int)BlackPawn];
5512 int seed, nrOfShuffles;
5513
5514 void GetPositionNumber()
5515 {       // sets global variable seed
5516         int i;
5517
5518         seed = appData.defaultFrcPosition;
5519         if(seed < 0) { // randomize based on time for negative FRC position numbers
5520                 for(i=0; i<50; i++) seed += random();
5521                 seed = random() ^ random() >> 8 ^ random() << 8;
5522                 if(seed<0) seed = -seed;
5523         }
5524 }
5525
5526 int put(Board board, int pieceType, int rank, int n, int shade)
5527 // put the piece on the (n-1)-th empty squares of the given shade
5528 {
5529         int i;
5530
5531         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5532                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5533                         board[rank][i] = (ChessSquare) pieceType;
5534                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5535                         squaresLeft[ANY]--;
5536                         piecesLeft[pieceType]--;
5537                         return i;
5538                 }
5539         }
5540         return -1;
5541 }
5542
5543
5544 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5545 // calculate where the next piece goes, (any empty square), and put it there
5546 {
5547         int i;
5548
5549         i = seed % squaresLeft[shade];
5550         nrOfShuffles *= squaresLeft[shade];
5551         seed /= squaresLeft[shade];
5552         put(board, pieceType, rank, i, shade);
5553 }
5554
5555 void AddTwoPieces(Board board, int pieceType, int rank)
5556 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5557 {
5558         int i, n=squaresLeft[ANY], j=n-1, k;
5559
5560         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5561         i = seed % k;  // pick one
5562         nrOfShuffles *= k;
5563         seed /= k;
5564         while(i >= j) i -= j--;
5565         j = n - 1 - j; i += j;
5566         put(board, pieceType, rank, j, ANY);
5567         put(board, pieceType, rank, i, ANY);
5568 }
5569
5570 void SetUpShuffle(Board board, int number)
5571 {
5572         int i, p, first=1;
5573
5574         GetPositionNumber(); nrOfShuffles = 1;
5575
5576         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5577         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5578         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5579
5580         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5581
5582         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5583             p = (int) board[0][i];
5584             if(p < (int) BlackPawn) piecesLeft[p] ++;
5585             board[0][i] = EmptySquare;
5586         }
5587
5588         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5589             // shuffles restricted to allow normal castling put KRR first
5590             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5591                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5593                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5595                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5596             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5597                 put(board, WhiteRook, 0, 0, ANY);
5598             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5599         }
5600
5601         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5602             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5603             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5604                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5605                 while(piecesLeft[p] >= 2) {
5606                     AddOnePiece(board, p, 0, LITE);
5607                     AddOnePiece(board, p, 0, DARK);
5608                 }
5609                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5610             }
5611
5612         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5613             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5614             // but we leave King and Rooks for last, to possibly obey FRC restriction
5615             if(p == (int)WhiteRook) continue;
5616             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5617             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5618         }
5619
5620         // now everything is placed, except perhaps King (Unicorn) and Rooks
5621
5622         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5623             // Last King gets castling rights
5624             while(piecesLeft[(int)WhiteUnicorn]) {
5625                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5626                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5627             }
5628
5629             while(piecesLeft[(int)WhiteKing]) {
5630                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634
5635         } else {
5636             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5637             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5638         }
5639
5640         // Only Rooks can be left; simply place them all
5641         while(piecesLeft[(int)WhiteRook]) {
5642                 i = put(board, WhiteRook, 0, 0, ANY);
5643                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5644                         if(first) {
5645                                 first=0;
5646                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5647                         }
5648                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5649                 }
5650         }
5651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5652             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5653         }
5654
5655         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5656 }
5657
5658 int SetCharTable( char *table, const char * map )
5659 /* [HGM] moved here from winboard.c because of its general usefulness */
5660 /*       Basically a safe strcpy that uses the last character as King */
5661 {
5662     int result = FALSE; int NrPieces;
5663
5664     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5665                     && NrPieces >= 12 && !(NrPieces&1)) {
5666         int i; /* [HGM] Accept even length from 12 to 34 */
5667
5668         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5669         for( i=0; i<NrPieces/2-1; i++ ) {
5670             table[i] = map[i];
5671             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5672         }
5673         table[(int) WhiteKing]  = map[NrPieces/2-1];
5674         table[(int) BlackKing]  = map[NrPieces-1];
5675
5676         result = TRUE;
5677     }
5678
5679     return result;
5680 }
5681
5682 void Prelude(Board board)
5683 {       // [HGM] superchess: random selection of exo-pieces
5684         int i, j, k; ChessSquare p;
5685         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5686
5687         GetPositionNumber(); // use FRC position number
5688
5689         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5690             SetCharTable(pieceToChar, appData.pieceToCharTable);
5691             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5692                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5693         }
5694
5695         j = seed%4;                 seed /= 4;
5696         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5697         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5698         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5699         j = seed%3 + (seed%3 >= j); seed /= 3;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3;                 seed /= 3;
5704         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%2 + (seed%2 >= j); seed /= 2;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5712         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5713         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5714         put(board, exoPieces[0],    0, 0, ANY);
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5716 }
5717
5718 void
5719 InitPosition(redraw)
5720      int redraw;
5721 {
5722     ChessSquare (* pieces)[BOARD_FILES];
5723     int i, j, pawnRow, overrule,
5724     oldx = gameInfo.boardWidth,
5725     oldy = gameInfo.boardHeight,
5726     oldh = gameInfo.holdingsWidth;
5727     static int oldv;
5728
5729     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5730
5731     /* [AS] Initialize pv info list [HGM] and game status */
5732     {
5733         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5734             pvInfoList[i].depth = 0;
5735             boards[i][EP_STATUS] = EP_NONE;
5736             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5737         }
5738
5739         initialRulePlies = 0; /* 50-move counter start */
5740
5741         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5742         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5743     }
5744
5745
5746     /* [HGM] logic here is completely changed. In stead of full positions */
5747     /* the initialized data only consist of the two backranks. The switch */
5748     /* selects which one we will use, which is than copied to the Board   */
5749     /* initialPosition, which for the rest is initialized by Pawns and    */
5750     /* empty squares. This initial position is then copied to boards[0],  */
5751     /* possibly after shuffling, so that it remains available.            */
5752
5753     gameInfo.holdingsWidth = 0; /* default board sizes */
5754     gameInfo.boardWidth    = 8;
5755     gameInfo.boardHeight   = 8;
5756     gameInfo.holdingsSize  = 0;
5757     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5758     for(i=0; i<BOARD_FILES-2; i++)
5759       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5760     initialPosition[EP_STATUS] = EP_NONE;
5761     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5762     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5763          SetCharTable(pieceNickName, appData.pieceNickNames);
5764     else SetCharTable(pieceNickName, "............");
5765     pieces = FIDEArray;
5766
5767     switch (gameInfo.variant) {
5768     case VariantFischeRandom:
5769       shuffleOpenings = TRUE;
5770     default:
5771       break;
5772     case VariantShatranj:
5773       pieces = ShatranjArray;
5774       nrCastlingRights = 0;
5775       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5776       break;
5777     case VariantMakruk:
5778       pieces = makrukArray;
5779       nrCastlingRights = 0;
5780       startedFromSetupPosition = TRUE;
5781       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5782       break;
5783     case VariantTwoKings:
5784       pieces = twoKingsArray;
5785       break;
5786     case VariantGrand:
5787       pieces = GrandArray;
5788       nrCastlingRights = 0;
5789       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5790       gameInfo.boardWidth = 10;
5791       gameInfo.boardHeight = 10;
5792       gameInfo.holdingsSize = 7;
5793       break;
5794     case VariantCapaRandom:
5795       shuffleOpenings = TRUE;
5796     case VariantCapablanca:
5797       pieces = CapablancaArray;
5798       gameInfo.boardWidth = 10;
5799       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5800       break;
5801     case VariantGothic:
5802       pieces = GothicArray;
5803       gameInfo.boardWidth = 10;
5804       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5805       break;
5806     case VariantSChess:
5807       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5808       gameInfo.holdingsSize = 7;
5809       break;
5810     case VariantJanus:
5811       pieces = JanusArray;
5812       gameInfo.boardWidth = 10;
5813       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5814       nrCastlingRights = 6;
5815         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5816         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5817         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5818         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5819         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5820         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5821       break;
5822     case VariantFalcon:
5823       pieces = FalconArray;
5824       gameInfo.boardWidth = 10;
5825       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5826       break;
5827     case VariantXiangqi:
5828       pieces = XiangqiArray;
5829       gameInfo.boardWidth  = 9;
5830       gameInfo.boardHeight = 10;
5831       nrCastlingRights = 0;
5832       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5833       break;
5834     case VariantShogi:
5835       pieces = ShogiArray;
5836       gameInfo.boardWidth  = 9;
5837       gameInfo.boardHeight = 9;
5838       gameInfo.holdingsSize = 7;
5839       nrCastlingRights = 0;
5840       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5841       break;
5842     case VariantCourier:
5843       pieces = CourierArray;
5844       gameInfo.boardWidth  = 12;
5845       nrCastlingRights = 0;
5846       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5847       break;
5848     case VariantKnightmate:
5849       pieces = KnightmateArray;
5850       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5851       break;
5852     case VariantSpartan:
5853       pieces = SpartanArray;
5854       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5855       break;
5856     case VariantFairy:
5857       pieces = fairyArray;
5858       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5859       break;
5860     case VariantGreat:
5861       pieces = GreatArray;
5862       gameInfo.boardWidth = 10;
5863       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5864       gameInfo.holdingsSize = 8;
5865       break;
5866     case VariantSuper:
5867       pieces = FIDEArray;
5868       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5869       gameInfo.holdingsSize = 8;
5870       startedFromSetupPosition = TRUE;
5871       break;
5872     case VariantCrazyhouse:
5873     case VariantBughouse:
5874       pieces = FIDEArray;
5875       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5876       gameInfo.holdingsSize = 5;
5877       break;
5878     case VariantWildCastle:
5879       pieces = FIDEArray;
5880       /* !!?shuffle with kings guaranteed to be on d or e file */
5881       shuffleOpenings = 1;
5882       break;
5883     case VariantNoCastle:
5884       pieces = FIDEArray;
5885       nrCastlingRights = 0;
5886       /* !!?unconstrained back-rank shuffle */
5887       shuffleOpenings = 1;
5888       break;
5889     }
5890
5891     overrule = 0;
5892     if(appData.NrFiles >= 0) {
5893         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5894         gameInfo.boardWidth = appData.NrFiles;
5895     }
5896     if(appData.NrRanks >= 0) {
5897         gameInfo.boardHeight = appData.NrRanks;
5898     }
5899     if(appData.holdingsSize >= 0) {
5900         i = appData.holdingsSize;
5901         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5902         gameInfo.holdingsSize = i;
5903     }
5904     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5905     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5906         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5907
5908     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5909     if(pawnRow < 1) pawnRow = 1;
5910     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5911
5912     /* User pieceToChar list overrules defaults */
5913     if(appData.pieceToCharTable != NULL)
5914         SetCharTable(pieceToChar, appData.pieceToCharTable);
5915
5916     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5917
5918         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5919             s = (ChessSquare) 0; /* account holding counts in guard band */
5920         for( i=0; i<BOARD_HEIGHT; i++ )
5921             initialPosition[i][j] = s;
5922
5923         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5924         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5925         initialPosition[pawnRow][j] = WhitePawn;
5926         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5927         if(gameInfo.variant == VariantXiangqi) {
5928             if(j&1) {
5929                 initialPosition[pawnRow][j] =
5930                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5931                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5932                    initialPosition[2][j] = WhiteCannon;
5933                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5934                 }
5935             }
5936         }
5937         if(gameInfo.variant == VariantGrand) {
5938             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5939                initialPosition[0][j] = WhiteRook;
5940                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5941             }
5942         }
5943         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5944     }
5945     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5946
5947             j=BOARD_LEFT+1;
5948             initialPosition[1][j] = WhiteBishop;
5949             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5950             j=BOARD_RGHT-2;
5951             initialPosition[1][j] = WhiteRook;
5952             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5953     }
5954
5955     if( nrCastlingRights == -1) {
5956         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5957         /*       This sets default castling rights from none to normal corners   */
5958         /* Variants with other castling rights must set them themselves above    */
5959         nrCastlingRights = 6;
5960
5961         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5962         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5963         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5964         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5965         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5966         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5967      }
5968
5969      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5970      if(gameInfo.variant == VariantGreat) { // promotion commoners
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5972         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5974         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5975      }
5976      if( gameInfo.variant == VariantSChess ) {
5977       initialPosition[1][0] = BlackMarshall;
5978       initialPosition[2][0] = BlackAngel;
5979       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5980       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5981       initialPosition[1][1] = initialPosition[2][1] = 
5982       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5983      }
5984   if (appData.debugMode) {
5985     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5986   }
5987     if(shuffleOpenings) {
5988         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5989         startedFromSetupPosition = TRUE;
5990     }
5991     if(startedFromPositionFile) {
5992       /* [HGM] loadPos: use PositionFile for every new game */
5993       CopyBoard(initialPosition, filePosition);
5994       for(i=0; i<nrCastlingRights; i++)
5995           initialRights[i] = filePosition[CASTLING][i];
5996       startedFromSetupPosition = TRUE;
5997     }
5998
5999     CopyBoard(boards[0], initialPosition);
6000
6001     if(oldx != gameInfo.boardWidth ||
6002        oldy != gameInfo.boardHeight ||
6003        oldv != gameInfo.variant ||
6004        oldh != gameInfo.holdingsWidth
6005                                          )
6006             InitDrawingSizes(-2 ,0);
6007
6008     oldv = gameInfo.variant;
6009     if (redraw)
6010       DrawPosition(TRUE, boards[currentMove]);
6011 }
6012
6013 void
6014 SendBoard(cps, moveNum)
6015      ChessProgramState *cps;
6016      int moveNum;
6017 {
6018     char message[MSG_SIZ];
6019
6020     if (cps->useSetboard) {
6021       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6022       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6023       SendToProgram(message, cps);
6024       free(fen);
6025
6026     } else {
6027       ChessSquare *bp;
6028       int i, j;
6029       /* Kludge to set black to move, avoiding the troublesome and now
6030        * deprecated "black" command.
6031        */
6032       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6033         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6034
6035       SendToProgram("edit\n", cps);
6036       SendToProgram("#\n", cps);
6037       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6038         bp = &boards[moveNum][i][BOARD_LEFT];
6039         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6040           if ((int) *bp < (int) BlackPawn) {
6041             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6042                     AAA + j, ONE + i);
6043             if(message[0] == '+' || message[0] == '~') {
6044               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6045                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6046                         AAA + j, ONE + i);
6047             }
6048             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6049                 message[1] = BOARD_RGHT   - 1 - j + '1';
6050                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6051             }
6052             SendToProgram(message, cps);
6053           }
6054         }
6055       }
6056
6057       SendToProgram("c\n", cps);
6058       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6059         bp = &boards[moveNum][i][BOARD_LEFT];
6060         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6061           if (((int) *bp != (int) EmptySquare)
6062               && ((int) *bp >= (int) BlackPawn)) {
6063             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6064                     AAA + j, ONE + i);
6065             if(message[0] == '+' || message[0] == '~') {
6066               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6067                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6068                         AAA + j, ONE + i);
6069             }
6070             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6071                 message[1] = BOARD_RGHT   - 1 - j + '1';
6072                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6073             }
6074             SendToProgram(message, cps);
6075           }
6076         }
6077       }
6078
6079       SendToProgram(".\n", cps);
6080     }
6081     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6082 }
6083
6084 ChessSquare
6085 DefaultPromoChoice(int white)
6086 {
6087     ChessSquare result;
6088     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6089         result = WhiteFerz; // no choice
6090     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6091         result= WhiteKing; // in Suicide Q is the last thing we want
6092     else if(gameInfo.variant == VariantSpartan)
6093         result = white ? WhiteQueen : WhiteAngel;
6094     else result = WhiteQueen;
6095     if(!white) result = WHITE_TO_BLACK result;
6096     return result;
6097 }
6098
6099 static int autoQueen; // [HGM] oneclick
6100
6101 int
6102 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6103 {
6104     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6105     /* [HGM] add Shogi promotions */
6106     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6107     ChessSquare piece;
6108     ChessMove moveType;
6109     Boolean premove;
6110
6111     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6112     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6113
6114     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6115       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6116         return FALSE;
6117
6118     piece = boards[currentMove][fromY][fromX];
6119     if(gameInfo.variant == VariantShogi) {
6120         promotionZoneSize = BOARD_HEIGHT/3;
6121         highestPromotingPiece = (int)WhiteFerz;
6122     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6123         promotionZoneSize = 3;
6124     }
6125
6126     // Treat Lance as Pawn when it is not representing Amazon
6127     if(gameInfo.variant != VariantSuper) {
6128         if(piece == WhiteLance) piece = WhitePawn; else
6129         if(piece == BlackLance) piece = BlackPawn;
6130     }
6131
6132     // next weed out all moves that do not touch the promotion zone at all
6133     if((int)piece >= BlackPawn) {
6134         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6135              return FALSE;
6136         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6137     } else {
6138         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6139            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6140     }
6141
6142     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6143
6144     // weed out mandatory Shogi promotions
6145     if(gameInfo.variant == VariantShogi) {
6146         if(piece >= BlackPawn) {
6147             if(toY == 0 && piece == BlackPawn ||
6148                toY == 0 && piece == BlackQueen ||
6149                toY <= 1 && piece == BlackKnight) {
6150                 *promoChoice = '+';
6151                 return FALSE;
6152             }
6153         } else {
6154             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6155                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6156                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6157                 *promoChoice = '+';
6158                 return FALSE;
6159             }
6160         }
6161     }
6162
6163     // weed out obviously illegal Pawn moves
6164     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6165         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6166         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6167         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6168         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6169         // note we are not allowed to test for valid (non-)capture, due to premove
6170     }
6171
6172     // we either have a choice what to promote to, or (in Shogi) whether to promote
6173     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6174         *promoChoice = PieceToChar(BlackFerz);  // no choice
6175         return FALSE;
6176     }
6177     // no sense asking what we must promote to if it is going to explode...
6178     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6179         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6180         return FALSE;
6181     }
6182     // give caller the default choice even if we will not make it
6183     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6184     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6185     if(        sweepSelect && gameInfo.variant != VariantGreat
6186                            && gameInfo.variant != VariantGrand
6187                            && gameInfo.variant != VariantSuper) return FALSE;
6188     if(autoQueen) return FALSE; // predetermined
6189
6190     // suppress promotion popup on illegal moves that are not premoves
6191     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6192               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6193     if(appData.testLegality && !premove) {
6194         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6195                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6196         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6197             return FALSE;
6198     }
6199
6200     return TRUE;
6201 }
6202
6203 int
6204 InPalace(row, column)
6205      int row, column;
6206 {   /* [HGM] for Xiangqi */
6207     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6208          column < (BOARD_WIDTH + 4)/2 &&
6209          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6210     return FALSE;
6211 }
6212
6213 int
6214 PieceForSquare (x, y)
6215      int x;
6216      int y;
6217 {
6218   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6219      return -1;
6220   else
6221      return boards[currentMove][y][x];
6222 }
6223
6224 int
6225 OKToStartUserMove(x, y)
6226      int x, y;
6227 {
6228     ChessSquare from_piece;
6229     int white_piece;
6230
6231     if (matchMode) return FALSE;
6232     if (gameMode == EditPosition) return TRUE;
6233
6234     if (x >= 0 && y >= 0)
6235       from_piece = boards[currentMove][y][x];
6236     else
6237       from_piece = EmptySquare;
6238
6239     if (from_piece == EmptySquare) return FALSE;
6240
6241     white_piece = (int)from_piece >= (int)WhitePawn &&
6242       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6243
6244     switch (gameMode) {
6245       case AnalyzeFile:
6246       case TwoMachinesPlay:
6247       case EndOfGame:
6248         return FALSE;
6249
6250       case IcsObserving:
6251       case IcsIdle:
6252         return FALSE;
6253
6254       case MachinePlaysWhite:
6255       case IcsPlayingBlack:
6256         if (appData.zippyPlay) return FALSE;
6257         if (white_piece) {
6258             DisplayMoveError(_("You are playing Black"));
6259             return FALSE;
6260         }
6261         break;
6262
6263       case MachinePlaysBlack:
6264       case IcsPlayingWhite:
6265         if (appData.zippyPlay) return FALSE;
6266         if (!white_piece) {
6267             DisplayMoveError(_("You are playing White"));
6268             return FALSE;
6269         }
6270         break;
6271
6272       case PlayFromGameFile:
6273             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6274       case EditGame:
6275         if (!white_piece && WhiteOnMove(currentMove)) {
6276             DisplayMoveError(_("It is White's turn"));
6277             return FALSE;
6278         }
6279         if (white_piece && !WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return FALSE;
6282         }
6283         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6284             /* Editing correspondence game history */
6285             /* Could disallow this or prompt for confirmation */
6286             cmailOldMove = -1;
6287         }
6288         break;
6289
6290       case BeginningOfGame:
6291         if (appData.icsActive) return FALSE;
6292         if (!appData.noChessProgram) {
6293             if (!white_piece) {
6294                 DisplayMoveError(_("You are playing White"));
6295                 return FALSE;
6296             }
6297         }
6298         break;
6299
6300       case Training:
6301         if (!white_piece && WhiteOnMove(currentMove)) {
6302             DisplayMoveError(_("It is White's turn"));
6303             return FALSE;
6304         }
6305         if (white_piece && !WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is Black's turn"));
6307             return FALSE;
6308         }
6309         break;
6310
6311       default:
6312       case IcsExamining:
6313         break;
6314     }
6315     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6316         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6317         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6318         && gameMode != AnalyzeFile && gameMode != Training) {
6319         DisplayMoveError(_("Displayed position is not current"));
6320         return FALSE;
6321     }
6322     return TRUE;
6323 }
6324
6325 Boolean
6326 OnlyMove(int *x, int *y, Boolean captures) {
6327     DisambiguateClosure cl;
6328     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6329     switch(gameMode) {
6330       case MachinePlaysBlack:
6331       case IcsPlayingWhite:
6332       case BeginningOfGame:
6333         if(!WhiteOnMove(currentMove)) return FALSE;
6334         break;
6335       case MachinePlaysWhite:
6336       case IcsPlayingBlack:
6337         if(WhiteOnMove(currentMove)) return FALSE;
6338         break;
6339       case EditGame:
6340         break;
6341       default:
6342         return FALSE;
6343     }
6344     cl.pieceIn = EmptySquare;
6345     cl.rfIn = *y;
6346     cl.ffIn = *x;
6347     cl.rtIn = -1;
6348     cl.ftIn = -1;
6349     cl.promoCharIn = NULLCHAR;
6350     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6351     if( cl.kind == NormalMove ||
6352         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6353         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6354         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355       fromX = cl.ff;
6356       fromY = cl.rf;
6357       *x = cl.ft;
6358       *y = cl.rt;
6359       return TRUE;
6360     }
6361     if(cl.kind != ImpossibleMove) return FALSE;
6362     cl.pieceIn = EmptySquare;
6363     cl.rfIn = -1;
6364     cl.ffIn = -1;
6365     cl.rtIn = *y;
6366     cl.ftIn = *x;
6367     cl.promoCharIn = NULLCHAR;
6368     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6369     if( cl.kind == NormalMove ||
6370         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6371         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6372         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6373       fromX = cl.ff;
6374       fromY = cl.rf;
6375       *x = cl.ft;
6376       *y = cl.rt;
6377       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6378       return TRUE;
6379     }
6380     return FALSE;
6381 }
6382
6383 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6384 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6385 int lastLoadGameUseList = FALSE;
6386 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6387 ChessMove lastLoadGameStart = EndOfFile;
6388
6389 void
6390 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6391      int fromX, fromY, toX, toY;
6392      int promoChar;
6393 {
6394     ChessMove moveType;
6395     ChessSquare pdown, pup;
6396
6397     /* Check if the user is playing in turn.  This is complicated because we
6398        let the user "pick up" a piece before it is his turn.  So the piece he
6399        tried to pick up may have been captured by the time he puts it down!
6400        Therefore we use the color the user is supposed to be playing in this
6401        test, not the color of the piece that is currently on the starting
6402        square---except in EditGame mode, where the user is playing both
6403        sides; fortunately there the capture race can't happen.  (It can
6404        now happen in IcsExamining mode, but that's just too bad.  The user
6405        will get a somewhat confusing message in that case.)
6406        */
6407
6408     switch (gameMode) {
6409       case AnalyzeFile:
6410       case TwoMachinesPlay:
6411       case EndOfGame:
6412       case IcsObserving:
6413       case IcsIdle:
6414         /* We switched into a game mode where moves are not accepted,
6415            perhaps while the mouse button was down. */
6416         return;
6417
6418       case MachinePlaysWhite:
6419         /* User is moving for Black */
6420         if (WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is White's turn"));
6422             return;
6423         }
6424         break;
6425
6426       case MachinePlaysBlack:
6427         /* User is moving for White */
6428         if (!WhiteOnMove(currentMove)) {
6429             DisplayMoveError(_("It is Black's turn"));
6430             return;
6431         }
6432         break;
6433
6434       case PlayFromGameFile:
6435             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6436       case EditGame:
6437       case IcsExamining:
6438       case BeginningOfGame:
6439       case AnalyzeMode:
6440       case Training:
6441         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6442         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6443             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6444             /* User is moving for Black */
6445             if (WhiteOnMove(currentMove)) {
6446                 DisplayMoveError(_("It is White's turn"));
6447                 return;
6448             }
6449         } else {
6450             /* User is moving for White */
6451             if (!WhiteOnMove(currentMove)) {
6452                 DisplayMoveError(_("It is Black's turn"));
6453                 return;
6454             }
6455         }
6456         break;
6457
6458       case IcsPlayingBlack:
6459         /* User is moving for Black */
6460         if (WhiteOnMove(currentMove)) {
6461             if (!appData.premove) {
6462                 DisplayMoveError(_("It is White's turn"));
6463             } else if (toX >= 0 && toY >= 0) {
6464                 premoveToX = toX;
6465                 premoveToY = toY;
6466                 premoveFromX = fromX;
6467                 premoveFromY = fromY;
6468                 premovePromoChar = promoChar;
6469                 gotPremove = 1;
6470                 if (appData.debugMode)
6471                     fprintf(debugFP, "Got premove: fromX %d,"
6472                             "fromY %d, toX %d, toY %d\n",
6473                             fromX, fromY, toX, toY);
6474             }
6475             return;
6476         }
6477         break;
6478
6479       case IcsPlayingWhite:
6480         /* User is moving for White */
6481         if (!WhiteOnMove(currentMove)) {
6482             if (!appData.premove) {
6483                 DisplayMoveError(_("It is Black's turn"));
6484             } else if (toX >= 0 && toY >= 0) {
6485                 premoveToX = toX;
6486                 premoveToY = toY;
6487                 premoveFromX = fromX;
6488                 premoveFromY = fromY;
6489                 premovePromoChar = promoChar;
6490                 gotPremove = 1;
6491                 if (appData.debugMode)
6492                     fprintf(debugFP, "Got premove: fromX %d,"
6493                             "fromY %d, toX %d, toY %d\n",
6494                             fromX, fromY, toX, toY);
6495             }
6496             return;
6497         }
6498         break;
6499
6500       default:
6501         break;
6502
6503       case EditPosition:
6504         /* EditPosition, empty square, or different color piece;
6505            click-click move is possible */
6506         if (toX == -2 || toY == -2) {
6507             boards[0][fromY][fromX] = EmptySquare;
6508             DrawPosition(FALSE, boards[currentMove]);
6509             return;
6510         } else if (toX >= 0 && toY >= 0) {
6511             boards[0][toY][toX] = boards[0][fromY][fromX];
6512             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6513                 if(boards[0][fromY][0] != EmptySquare) {
6514                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6515                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6516                 }
6517             } else
6518             if(fromX == BOARD_RGHT+1) {
6519                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6520                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6521                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6522                 }
6523             } else
6524             boards[0][fromY][fromX] = EmptySquare;
6525             DrawPosition(FALSE, boards[currentMove]);
6526             return;
6527         }
6528         return;
6529     }
6530
6531     if(toX < 0 || toY < 0) return;
6532     pdown = boards[currentMove][fromY][fromX];
6533     pup = boards[currentMove][toY][toX];
6534
6535     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6536     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6537          if( pup != EmptySquare ) return;
6538          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6539            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6540                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6541            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6542            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6543            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6544            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6545          fromY = DROP_RANK;
6546     }
6547
6548     /* [HGM] always test for legality, to get promotion info */
6549     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6550                                          fromY, fromX, toY, toX, promoChar);
6551
6552     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6553
6554     /* [HGM] but possibly ignore an IllegalMove result */
6555     if (appData.testLegality) {
6556         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6557             DisplayMoveError(_("Illegal move"));
6558             return;
6559         }
6560     }
6561
6562     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6563 }
6564
6565 /* Common tail of UserMoveEvent and DropMenuEvent */
6566 int
6567 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6568      ChessMove moveType;
6569      int fromX, fromY, toX, toY;
6570      /*char*/int promoChar;
6571 {
6572     char *bookHit = 0;
6573
6574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6575         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6576         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6577         if(WhiteOnMove(currentMove)) {
6578             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6579         } else {
6580             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6581         }
6582     }
6583
6584     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6585        move type in caller when we know the move is a legal promotion */
6586     if(moveType == NormalMove && promoChar)
6587         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6588
6589     /* [HGM] <popupFix> The following if has been moved here from
6590        UserMoveEvent(). Because it seemed to belong here (why not allow
6591        piece drops in training games?), and because it can only be
6592        performed after it is known to what we promote. */
6593     if (gameMode == Training) {
6594       /* compare the move played on the board to the next move in the
6595        * game. If they match, display the move and the opponent's response.
6596        * If they don't match, display an error message.
6597        */
6598       int saveAnimate;
6599       Board testBoard;
6600       CopyBoard(testBoard, boards[currentMove]);
6601       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6602
6603       if (CompareBoards(testBoard, boards[currentMove+1])) {
6604         ForwardInner(currentMove+1);
6605
6606         /* Autoplay the opponent's response.
6607          * if appData.animate was TRUE when Training mode was entered,
6608          * the response will be animated.
6609          */
6610         saveAnimate = appData.animate;
6611         appData.animate = animateTraining;
6612         ForwardInner(currentMove+1);
6613         appData.animate = saveAnimate;
6614
6615         /* check for the end of the game */
6616         if (currentMove >= forwardMostMove) {
6617           gameMode = PlayFromGameFile;
6618           ModeHighlight();
6619           SetTrainingModeOff();
6620           DisplayInformation(_("End of game"));
6621         }
6622       } else {
6623         DisplayError(_("Incorrect move"), 0);
6624       }
6625       return 1;
6626     }
6627
6628   /* Ok, now we know that the move is good, so we can kill
6629      the previous line in Analysis Mode */
6630   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6631                                 && currentMove < forwardMostMove) {
6632     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6633     else forwardMostMove = currentMove;
6634   }
6635
6636   /* If we need the chess program but it's dead, restart it */
6637   ResurrectChessProgram();
6638
6639   /* A user move restarts a paused game*/
6640   if (pausing)
6641     PauseEvent();
6642
6643   thinkOutput[0] = NULLCHAR;
6644
6645   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6646
6647   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6648     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6649     return 1;
6650   }
6651
6652   if (gameMode == BeginningOfGame) {
6653     if (appData.noChessProgram) {
6654       gameMode = EditGame;
6655       SetGameInfo();
6656     } else {
6657       char buf[MSG_SIZ];
6658       gameMode = MachinePlaysBlack;
6659       StartClocks();
6660       SetGameInfo();
6661       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6662       DisplayTitle(buf);
6663       if (first.sendName) {
6664         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6665         SendToProgram(buf, &first);
6666       }
6667       StartClocks();
6668     }
6669     ModeHighlight();
6670   }
6671
6672   /* Relay move to ICS or chess engine */
6673   if (appData.icsActive) {
6674     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6675         gameMode == IcsExamining) {
6676       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6677         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6678         SendToICS("draw ");
6679         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6680       }
6681       // also send plain move, in case ICS does not understand atomic claims
6682       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6683       ics_user_moved = 1;
6684     }
6685   } else {
6686     if (first.sendTime && (gameMode == BeginningOfGame ||
6687                            gameMode == MachinePlaysWhite ||
6688                            gameMode == MachinePlaysBlack)) {
6689       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6690     }
6691     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6692          // [HGM] book: if program might be playing, let it use book
6693         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6694         first.maybeThinking = TRUE;
6695     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6696         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6697         SendBoard(&first, currentMove+1);
6698     } else SendMoveToProgram(forwardMostMove-1, &first);
6699     if (currentMove == cmailOldMove + 1) {
6700       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6701     }
6702   }
6703
6704   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6705
6706   switch (gameMode) {
6707   case EditGame:
6708     if(appData.testLegality)
6709     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6710     case MT_NONE:
6711     case MT_CHECK:
6712       break;
6713     case MT_CHECKMATE:
6714     case MT_STAINMATE:
6715       if (WhiteOnMove(currentMove)) {
6716         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6717       } else {
6718         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6719       }
6720       break;
6721     case MT_STALEMATE:
6722       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6723       break;
6724     }
6725     break;
6726
6727   case MachinePlaysBlack:
6728   case MachinePlaysWhite:
6729     /* disable certain menu options while machine is thinking */
6730     SetMachineThinkingEnables();
6731     break;
6732
6733   default:
6734     break;
6735   }
6736
6737   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6738   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6739
6740   if(bookHit) { // [HGM] book: simulate book reply
6741         static char bookMove[MSG_SIZ]; // a bit generous?
6742
6743         programStats.nodes = programStats.depth = programStats.time =
6744         programStats.score = programStats.got_only_move = 0;
6745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6746
6747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6748         strcat(bookMove, bookHit);
6749         HandleMachineMove(bookMove, &first);
6750   }
6751   return 1;
6752 }
6753
6754 void
6755 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6756      Board board;
6757      int flags;
6758      ChessMove kind;
6759      int rf, ff, rt, ft;
6760      VOIDSTAR closure;
6761 {
6762     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6763     Markers *m = (Markers *) closure;
6764     if(rf == fromY && ff == fromX)
6765         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6766                          || kind == WhiteCapturesEnPassant
6767                          || kind == BlackCapturesEnPassant);
6768     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6769 }
6770
6771 void
6772 MarkTargetSquares(int clear)
6773 {
6774   int x, y;
6775   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6776      !appData.testLegality || gameMode == EditPosition) return;
6777   if(clear) {
6778     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6779   } else {
6780     int capt = 0;
6781     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6782     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6783       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6784       if(capt)
6785       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6786     }
6787   }
6788   DrawPosition(TRUE, NULL);
6789 }
6790
6791 int
6792 Explode(Board board, int fromX, int fromY, int toX, int toY)
6793 {
6794     if(gameInfo.variant == VariantAtomic &&
6795        (board[toY][toX] != EmptySquare ||                     // capture?
6796         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6797                          board[fromY][fromX] == BlackPawn   )
6798       )) {
6799         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6800         return TRUE;
6801     }
6802     return FALSE;
6803 }
6804
6805 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6806
6807 int CanPromote(ChessSquare piece, int y)
6808 {
6809         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6810         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6811         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6812            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6813            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6814                                                   gameInfo.variant == VariantMakruk) return FALSE;
6815         return (piece == BlackPawn && y == 1 ||
6816                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6817                 piece == BlackLance && y == 1 ||
6818                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6819 }
6820
6821 void LeftClick(ClickType clickType, int xPix, int yPix)
6822 {
6823     int x, y;
6824     Boolean saveAnimate;
6825     static int second = 0, promotionChoice = 0, clearFlag = 0;
6826     char promoChoice = NULLCHAR;
6827     ChessSquare piece;
6828
6829     if(appData.seekGraph && appData.icsActive && loggedOn &&
6830         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6831         SeekGraphClick(clickType, xPix, yPix, 0);
6832         return;
6833     }
6834
6835     if (clickType == Press) ErrorPopDown();
6836
6837     x = EventToSquare(xPix, BOARD_WIDTH);
6838     y = EventToSquare(yPix, BOARD_HEIGHT);
6839     if (!flipView && y >= 0) {
6840         y = BOARD_HEIGHT - 1 - y;
6841     }
6842     if (flipView && x >= 0) {
6843         x = BOARD_WIDTH - 1 - x;
6844     }
6845
6846     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6847         defaultPromoChoice = promoSweep;
6848         promoSweep = EmptySquare;   // terminate sweep
6849         promoDefaultAltered = TRUE;
6850         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6851     }
6852
6853     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6854         if(clickType == Release) return; // ignore upclick of click-click destination
6855         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6856         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6857         if(gameInfo.holdingsWidth &&
6858                 (WhiteOnMove(currentMove)
6859                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6860                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6861             // click in right holdings, for determining promotion piece
6862             ChessSquare p = boards[currentMove][y][x];
6863             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6864             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6865             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6866                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6867                 fromX = fromY = -1;
6868                 return;
6869             }
6870         }
6871         DrawPosition(FALSE, boards[currentMove]);
6872         return;
6873     }
6874
6875     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6876     if(clickType == Press
6877             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6878               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6879               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6880         return;
6881
6882     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6883         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6884
6885     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6886         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6887                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6888         defaultPromoChoice = DefaultPromoChoice(side);
6889     }
6890
6891     autoQueen = appData.alwaysPromoteToQueen;
6892
6893     if (fromX == -1) {
6894       int originalY = y;
6895       gatingPiece = EmptySquare;
6896       if (clickType != Press) {
6897         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6898             DragPieceEnd(xPix, yPix); dragging = 0;
6899             DrawPosition(FALSE, NULL);
6900         }
6901         return;
6902       }
6903       fromX = x; fromY = y; toX = toY = -1;
6904       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6905          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6906          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6907             /* First square */
6908             if (OKToStartUserMove(fromX, fromY)) {
6909                 second = 0;
6910                 MarkTargetSquares(0);
6911                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6912                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6913                     promoSweep = defaultPromoChoice;
6914                     selectFlag = 0; lastX = xPix; lastY = yPix;
6915                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6916                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6917                 }
6918                 if (appData.highlightDragging) {
6919                     SetHighlights(fromX, fromY, -1, -1);
6920                 }
6921             } else fromX = fromY = -1;
6922             return;
6923         }
6924     }
6925
6926     /* fromX != -1 */
6927     if (clickType == Press && gameMode != EditPosition) {
6928         ChessSquare fromP;
6929         ChessSquare toP;
6930         int frc;
6931
6932         // ignore off-board to clicks
6933         if(y < 0 || x < 0) return;
6934
6935         /* Check if clicking again on the same color piece */
6936         fromP = boards[currentMove][fromY][fromX];
6937         toP = boards[currentMove][y][x];
6938         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6939         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6940              WhitePawn <= toP && toP <= WhiteKing &&
6941              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6942              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6943             (BlackPawn <= fromP && fromP <= BlackKing &&
6944              BlackPawn <= toP && toP <= BlackKing &&
6945              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6946              !(fromP == BlackKing && toP == BlackRook && frc))) {
6947             /* Clicked again on same color piece -- changed his mind */
6948             second = (x == fromX && y == fromY);
6949             promoDefaultAltered = FALSE;
6950             MarkTargetSquares(1);
6951            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6952             if (appData.highlightDragging) {
6953                 SetHighlights(x, y, -1, -1);
6954             } else {
6955                 ClearHighlights();
6956             }
6957             if (OKToStartUserMove(x, y)) {
6958                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6959                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6960                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6961                  gatingPiece = boards[currentMove][fromY][fromX];
6962                 else gatingPiece = EmptySquare;
6963                 fromX = x;
6964                 fromY = y; dragging = 1;
6965                 MarkTargetSquares(0);
6966                 DragPieceBegin(xPix, yPix, FALSE);
6967                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6968                     promoSweep = defaultPromoChoice;
6969                     selectFlag = 0; lastX = xPix; lastY = yPix;
6970                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6971                 }
6972             }
6973            }
6974            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6975            second = FALSE; 
6976         }
6977         // ignore clicks on holdings
6978         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6979     }
6980
6981     if (clickType == Release && x == fromX && y == fromY) {
6982         DragPieceEnd(xPix, yPix); dragging = 0;
6983         if(clearFlag) {
6984             // a deferred attempt to click-click move an empty square on top of a piece
6985             boards[currentMove][y][x] = EmptySquare;
6986             ClearHighlights();
6987             DrawPosition(FALSE, boards[currentMove]);
6988             fromX = fromY = -1; clearFlag = 0;
6989             return;
6990         }
6991         if (appData.animateDragging) {
6992             /* Undo animation damage if any */
6993             DrawPosition(FALSE, NULL);
6994         }
6995         if (second) {
6996             /* Second up/down in same square; just abort move */
6997             second = 0;
6998             fromX = fromY = -1;
6999             gatingPiece = EmptySquare;
7000             ClearHighlights();
7001             gotPremove = 0;
7002             ClearPremoveHighlights();
7003         } else {
7004             /* First upclick in same square; start click-click mode */
7005             SetHighlights(x, y, -1, -1);
7006         }
7007         return;
7008     }
7009
7010     clearFlag = 0;
7011
7012     /* we now have a different from- and (possibly off-board) to-square */
7013     /* Completed move */
7014     toX = x;
7015     toY = y;
7016     saveAnimate = appData.animate;
7017     MarkTargetSquares(1);
7018     if (clickType == Press) {
7019         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7020             // must be Edit Position mode with empty-square selected
7021             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7022             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7023             return;
7024         }
7025         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7026             ChessSquare piece = boards[currentMove][fromY][fromX];
7027             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7028             promoSweep = defaultPromoChoice;
7029             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7030             selectFlag = 0; lastX = xPix; lastY = yPix;
7031             Sweep(0); // Pawn that is going to promote: preview promotion piece
7032             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7033             DrawPosition(FALSE, boards[currentMove]);
7034             return;
7035         }
7036         /* Finish clickclick move */
7037         if (appData.animate || appData.highlightLastMove) {
7038             SetHighlights(fromX, fromY, toX, toY);
7039         } else {
7040             ClearHighlights();
7041         }
7042     } else {
7043         /* Finish drag move */
7044         if (appData.highlightLastMove) {
7045             SetHighlights(fromX, fromY, toX, toY);
7046         } else {
7047             ClearHighlights();
7048         }
7049         DragPieceEnd(xPix, yPix); dragging = 0;
7050         /* Don't animate move and drag both */
7051         appData.animate = FALSE;
7052     }
7053
7054     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7055     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7056         ChessSquare piece = boards[currentMove][fromY][fromX];
7057         if(gameMode == EditPosition && piece != EmptySquare &&
7058            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7059             int n;
7060
7061             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7062                 n = PieceToNumber(piece - (int)BlackPawn);
7063                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7064                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7065                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7066             } else
7067             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7068                 n = PieceToNumber(piece);
7069                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7070                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7071                 boards[currentMove][n][BOARD_WIDTH-2]++;
7072             }
7073             boards[currentMove][fromY][fromX] = EmptySquare;
7074         }
7075         ClearHighlights();
7076         fromX = fromY = -1;
7077         DrawPosition(TRUE, boards[currentMove]);
7078         return;
7079     }
7080
7081     // off-board moves should not be highlighted
7082     if(x < 0 || y < 0) ClearHighlights();
7083
7084     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7085
7086     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7087         SetHighlights(fromX, fromY, toX, toY);
7088         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7089             // [HGM] super: promotion to captured piece selected from holdings
7090             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7091             promotionChoice = TRUE;
7092             // kludge follows to temporarily execute move on display, without promoting yet
7093             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7094             boards[currentMove][toY][toX] = p;
7095             DrawPosition(FALSE, boards[currentMove]);
7096             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7097             boards[currentMove][toY][toX] = q;
7098             DisplayMessage("Click in holdings to choose piece", "");
7099             return;
7100         }
7101         PromotionPopUp();
7102     } else {
7103         int oldMove = currentMove;
7104         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7105         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7106         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7107         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7108            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7109             DrawPosition(TRUE, boards[currentMove]);
7110         fromX = fromY = -1;
7111     }
7112     appData.animate = saveAnimate;
7113     if (appData.animate || appData.animateDragging) {
7114         /* Undo animation damage if needed */
7115         DrawPosition(FALSE, NULL);
7116     }
7117 }
7118
7119 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7120 {   // front-end-free part taken out of PieceMenuPopup
7121     int whichMenu; int xSqr, ySqr;
7122
7123     if(seekGraphUp) { // [HGM] seekgraph
7124         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7125         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7126         return -2;
7127     }
7128
7129     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7130          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7131         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7132         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7133         if(action == Press)   {
7134             originalFlip = flipView;
7135             flipView = !flipView; // temporarily flip board to see game from partners perspective
7136             DrawPosition(TRUE, partnerBoard);
7137             DisplayMessage(partnerStatus, "");
7138             partnerUp = TRUE;
7139         } else if(action == Release) {
7140             flipView = originalFlip;
7141             DrawPosition(TRUE, boards[currentMove]);
7142             partnerUp = FALSE;
7143         }
7144         return -2;
7145     }
7146
7147     xSqr = EventToSquare(x, BOARD_WIDTH);
7148     ySqr = EventToSquare(y, BOARD_HEIGHT);
7149     if (action == Release) {
7150         if(pieceSweep != EmptySquare) {
7151             EditPositionMenuEvent(pieceSweep, toX, toY);
7152             pieceSweep = EmptySquare;
7153         } else UnLoadPV(); // [HGM] pv
7154     }
7155     if (action != Press) return -2; // return code to be ignored
7156     switch (gameMode) {
7157       case IcsExamining:
7158         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7159       case EditPosition:
7160         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7161         if (xSqr < 0 || ySqr < 0) return -1;
7162         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7163         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7164         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7165         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7166         NextPiece(0);
7167         return 2; // grab
7168       case IcsObserving:
7169         if(!appData.icsEngineAnalyze) return -1;
7170       case IcsPlayingWhite:
7171       case IcsPlayingBlack:
7172         if(!appData.zippyPlay) goto noZip;
7173       case AnalyzeMode:
7174       case AnalyzeFile:
7175       case MachinePlaysWhite:
7176       case MachinePlaysBlack:
7177       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7178         if (!appData.dropMenu) {
7179           LoadPV(x, y);
7180           return 2; // flag front-end to grab mouse events
7181         }
7182         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7183            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7184       case EditGame:
7185       noZip:
7186         if (xSqr < 0 || ySqr < 0) return -1;
7187         if (!appData.dropMenu || appData.testLegality &&
7188             gameInfo.variant != VariantBughouse &&
7189             gameInfo.variant != VariantCrazyhouse) return -1;
7190         whichMenu = 1; // drop menu
7191         break;
7192       default:
7193         return -1;
7194     }
7195
7196     if (((*fromX = xSqr) < 0) ||
7197         ((*fromY = ySqr) < 0)) {
7198         *fromX = *fromY = -1;
7199         return -1;
7200     }
7201     if (flipView)
7202       *fromX = BOARD_WIDTH - 1 - *fromX;
7203     else
7204       *fromY = BOARD_HEIGHT - 1 - *fromY;
7205
7206     return whichMenu;
7207 }
7208
7209 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7210 {
7211 //    char * hint = lastHint;
7212     FrontEndProgramStats stats;
7213
7214     stats.which = cps == &first ? 0 : 1;
7215     stats.depth = cpstats->depth;
7216     stats.nodes = cpstats->nodes;
7217     stats.score = cpstats->score;
7218     stats.time = cpstats->time;
7219     stats.pv = cpstats->movelist;
7220     stats.hint = lastHint;
7221     stats.an_move_index = 0;
7222     stats.an_move_count = 0;
7223
7224     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7225         stats.hint = cpstats->move_name;
7226         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7227         stats.an_move_count = cpstats->nr_moves;
7228     }
7229
7230     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7231
7232     SetProgramStats( &stats );
7233 }
7234
7235 void
7236 ClearEngineOutputPane(int which)
7237 {
7238     static FrontEndProgramStats dummyStats;
7239     dummyStats.which = which;
7240     dummyStats.pv = "#";
7241     SetProgramStats( &dummyStats );
7242 }
7243
7244 #define MAXPLAYERS 500
7245
7246 char *
7247 TourneyStandings(int display)
7248 {
7249     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7250     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7251     char result, *p, *names[MAXPLAYERS];
7252
7253     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7254         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7255     names[0] = p = strdup(appData.participants);
7256     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7257
7258     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7259
7260     while(result = appData.results[nr]) {
7261         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7262         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7263         wScore = bScore = 0;
7264         switch(result) {
7265           case '+': wScore = 2; break;
7266           case '-': bScore = 2; break;
7267           case '=': wScore = bScore = 1; break;
7268           case ' ':
7269           case '*': return strdup("busy"); // tourney not finished
7270         }
7271         score[w] += wScore;
7272         score[b] += bScore;
7273         games[w]++;
7274         games[b]++;
7275         nr++;
7276     }
7277     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7278     for(w=0; w<nPlayers; w++) {
7279         bScore = -1;
7280         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7281         ranking[w] = b; points[w] = bScore; score[b] = -2;
7282     }
7283     p = malloc(nPlayers*34+1);
7284     for(w=0; w<nPlayers && w<display; w++)
7285         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7286     free(names[0]);
7287     return p;
7288 }
7289
7290 void
7291 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7292 {       // count all piece types
7293         int p, f, r;
7294         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7295         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7297                 p = board[r][f];
7298                 pCnt[p]++;
7299                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7300                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7301                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7302                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7303                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7304                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7305         }
7306 }
7307
7308 int
7309 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7310 {
7311         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7312         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7313
7314         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7315         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7316         if(myPawns == 2 && nMine == 3) // KPP
7317             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7318         if(myPawns == 1 && nMine == 2) // KP
7319             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7320         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7321             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7322         if(myPawns) return FALSE;
7323         if(pCnt[WhiteRook+side])
7324             return pCnt[BlackRook-side] ||
7325                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7326                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7327                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7328         if(pCnt[WhiteCannon+side]) {
7329             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7330             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7331         }
7332         if(pCnt[WhiteKnight+side])
7333             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7334         return FALSE;
7335 }
7336
7337 int
7338 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7339 {
7340         VariantClass v = gameInfo.variant;
7341
7342         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7343         if(v == VariantShatranj) return TRUE; // always winnable through baring
7344         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7345         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7346
7347         if(v == VariantXiangqi) {
7348                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7349
7350                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7351                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7352                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7353                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7354                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7355                 if(stale) // we have at least one last-rank P plus perhaps C
7356                     return majors // KPKX
7357                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7358                 else // KCA*E*
7359                     return pCnt[WhiteFerz+side] // KCAK
7360                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7361                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7362                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7363
7364         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7365                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7366
7367                 if(nMine == 1) return FALSE; // bare King
7368                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7369                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7370                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7371                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7372                 if(pCnt[WhiteKnight+side])
7373                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7374                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7375                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7376                 if(nBishops)
7377                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7378                 if(pCnt[WhiteAlfil+side])
7379                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7380                 if(pCnt[WhiteWazir+side])
7381                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7382         }
7383
7384         return TRUE;
7385 }
7386
7387 int
7388 CompareWithRights(Board b1, Board b2)
7389 {
7390     int rights = 0;
7391     if(!CompareBoards(b1, b2)) return FALSE;
7392     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7393     /* compare castling rights */
7394     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7395            rights++; /* King lost rights, while rook still had them */
7396     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7397         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7398            rights++; /* but at least one rook lost them */
7399     }
7400     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7401            rights++;
7402     if( b1[CASTLING][5] != NoRights ) {
7403         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7404            rights++;
7405     }
7406     return rights == 0;
7407 }
7408
7409 int
7410 Adjudicate(ChessProgramState *cps)
7411 {       // [HGM] some adjudications useful with buggy engines
7412         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7413         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7414         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7415         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7416         int k, count = 0; static int bare = 1;
7417         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7418         Boolean canAdjudicate = !appData.icsActive;
7419
7420         // most tests only when we understand the game, i.e. legality-checking on
7421             if( appData.testLegality )
7422             {   /* [HGM] Some more adjudications for obstinate engines */
7423                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7424                 static int moveCount = 6;
7425                 ChessMove result;
7426                 char *reason = NULL;
7427
7428                 /* Count what is on board. */
7429                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7430
7431                 /* Some material-based adjudications that have to be made before stalemate test */
7432                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7433                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7434                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7435                      if(canAdjudicate && appData.checkMates) {
7436                          if(engineOpponent)
7437                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7438                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7439                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7440                          return 1;
7441                      }
7442                 }
7443
7444                 /* Bare King in Shatranj (loses) or Losers (wins) */
7445                 if( nrW == 1 || nrB == 1) {
7446                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7447                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7448                      if(canAdjudicate && appData.checkMates) {
7449                          if(engineOpponent)
7450                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7451                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7452                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7453                          return 1;
7454                      }
7455                   } else
7456                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7457                   {    /* bare King */
7458                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7459                         if(canAdjudicate && appData.checkMates) {
7460                             /* but only adjudicate if adjudication enabled */
7461                             if(engineOpponent)
7462                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7463                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                             return 1;
7466                         }
7467                   }
7468                 } else bare = 1;
7469
7470
7471             // don't wait for engine to announce game end if we can judge ourselves
7472             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7473               case MT_CHECK:
7474                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7475                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7476                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7477                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7478                             checkCnt++;
7479                         if(checkCnt >= 2) {
7480                             reason = "Xboard adjudication: 3rd check";
7481                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7482                             break;
7483                         }
7484                     }
7485                 }
7486               case MT_NONE:
7487               default:
7488                 break;
7489               case MT_STALEMATE:
7490               case MT_STAINMATE:
7491                 reason = "Xboard adjudication: Stalemate";
7492                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7493                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7494                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7495                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7496                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7497                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7498                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7499                                                                         EP_CHECKMATE : EP_WINS);
7500                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7501                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7502                 }
7503                 break;
7504               case MT_CHECKMATE:
7505                 reason = "Xboard adjudication: Checkmate";
7506                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7507                 break;
7508             }
7509
7510                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7511                     case EP_STALEMATE:
7512                         result = GameIsDrawn; break;
7513                     case EP_CHECKMATE:
7514                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7515                     case EP_WINS:
7516                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7517                     default:
7518                         result = EndOfFile;
7519                 }
7520                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7521                     if(engineOpponent)
7522                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                     GameEnds( result, reason, GE_XBOARD );
7524                     return 1;
7525                 }
7526
7527                 /* Next absolutely insufficient mating material. */
7528                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7529                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7530                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7531
7532                      /* always flag draws, for judging claims */
7533                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7534
7535                      if(canAdjudicate && appData.materialDraws) {
7536                          /* but only adjudicate them if adjudication enabled */
7537                          if(engineOpponent) {
7538                            SendToProgram("force\n", engineOpponent); // suppress reply
7539                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7540                          }
7541                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7542                          return 1;
7543                      }
7544                 }
7545
7546                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7547                 if(gameInfo.variant == VariantXiangqi ?
7548                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7549                  : nrW + nrB == 4 &&
7550                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7551                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7552                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7553                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7554                    ) ) {
7555                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7556                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7557                           if(engineOpponent) {
7558                             SendToProgram("force\n", engineOpponent); // suppress reply
7559                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                           }
7561                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7562                           return 1;
7563                      }
7564                 } else moveCount = 6;
7565             }
7566         if (appData.debugMode) { int i;
7567             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7568                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7569                     appData.drawRepeats);
7570             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7571               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7572
7573         }
7574
7575         // Repetition draws and 50-move rule can be applied independently of legality testing
7576
7577                 /* Check for rep-draws */
7578                 count = 0;
7579                 for(k = forwardMostMove-2;
7580                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7581                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7582                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7583                     k-=2)
7584                 {   int rights=0;
7585                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7586                         /* compare castling rights */
7587                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7588                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7589                                 rights++; /* King lost rights, while rook still had them */
7590                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7591                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7592                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7593                                    rights++; /* but at least one rook lost them */
7594                         }
7595                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7596                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7597                                 rights++;
7598                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7599                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7600                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7601                                    rights++;
7602                         }
7603                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7604                             && appData.drawRepeats > 1) {
7605                              /* adjudicate after user-specified nr of repeats */
7606                              int result = GameIsDrawn;
7607                              char *details = "XBoard adjudication: repetition draw";
7608                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7609                                 // [HGM] xiangqi: check for forbidden perpetuals
7610                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7611                                 for(m=forwardMostMove; m>k; m-=2) {
7612                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7613                                         ourPerpetual = 0; // the current mover did not always check
7614                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7615                                         hisPerpetual = 0; // the opponent did not always check
7616                                 }
7617                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7618                                                                         ourPerpetual, hisPerpetual);
7619                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7620                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7621                                     details = "Xboard adjudication: perpetual checking";
7622                                 } else
7623                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7624                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7625                                 } else
7626                                 // Now check for perpetual chases
7627                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7628                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7629                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7630                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7631                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7632                                         details = "Xboard adjudication: perpetual chasing";
7633                                     } else
7634                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7635                                         break; // Abort repetition-checking loop.
7636                                 }
7637                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7638                              }
7639                              if(engineOpponent) {
7640                                SendToProgram("force\n", engineOpponent); // suppress reply
7641                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7642                              }
7643                              GameEnds( result, details, GE_XBOARD );
7644                              return 1;
7645                         }
7646                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7647                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7648                     }
7649                 }
7650
7651                 /* Now we test for 50-move draws. Determine ply count */
7652                 count = forwardMostMove;
7653                 /* look for last irreversble move */
7654                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7655                     count--;
7656                 /* if we hit starting position, add initial plies */
7657                 if( count == backwardMostMove )
7658                     count -= initialRulePlies;
7659                 count = forwardMostMove - count;
7660                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7661                         // adjust reversible move counter for checks in Xiangqi
7662                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7663                         if(i < backwardMostMove) i = backwardMostMove;
7664                         while(i <= forwardMostMove) {
7665                                 lastCheck = inCheck; // check evasion does not count
7666                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7667                                 if(inCheck || lastCheck) count--; // check does not count
7668                                 i++;
7669                         }
7670                 }
7671                 if( count >= 100)
7672                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7673                          /* this is used to judge if draw claims are legal */
7674                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7675                          if(engineOpponent) {
7676                            SendToProgram("force\n", engineOpponent); // suppress reply
7677                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                          }
7679                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7680                          return 1;
7681                 }
7682
7683                 /* if draw offer is pending, treat it as a draw claim
7684                  * when draw condition present, to allow engines a way to
7685                  * claim draws before making their move to avoid a race
7686                  * condition occurring after their move
7687                  */
7688                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7689                          char *p = NULL;
7690                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7691                              p = "Draw claim: 50-move rule";
7692                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7693                              p = "Draw claim: 3-fold repetition";
7694                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7695                              p = "Draw claim: insufficient mating material";
7696                          if( p != NULL && canAdjudicate) {
7697                              if(engineOpponent) {
7698                                SendToProgram("force\n", engineOpponent); // suppress reply
7699                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7700                              }
7701                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7702                              return 1;
7703                          }
7704                 }
7705
7706                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7707                     if(engineOpponent) {
7708                       SendToProgram("force\n", engineOpponent); // suppress reply
7709                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7710                     }
7711                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7712                     return 1;
7713                 }
7714         return 0;
7715 }
7716
7717 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7718 {   // [HGM] book: this routine intercepts moves to simulate book replies
7719     char *bookHit = NULL;
7720
7721     //first determine if the incoming move brings opponent into his book
7722     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7723         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7724     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7725     if(bookHit != NULL && !cps->bookSuspend) {
7726         // make sure opponent is not going to reply after receiving move to book position
7727         SendToProgram("force\n", cps);
7728         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7729     }
7730     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7731     // now arrange restart after book miss
7732     if(bookHit) {
7733         // after a book hit we never send 'go', and the code after the call to this routine
7734         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7735         char buf[MSG_SIZ], *move = bookHit;
7736         if(cps->useSAN) {
7737             int fromX, fromY, toX, toY;
7738             char promoChar;
7739             ChessMove moveType;
7740             move = buf + 30;
7741             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7742                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7743                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7744                                     PosFlags(forwardMostMove),
7745                                     fromY, fromX, toY, toX, promoChar, move);
7746             } else {
7747                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7748                 bookHit = NULL;
7749             }
7750         }
7751         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7752         SendToProgram(buf, cps);
7753         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7754     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7755         SendToProgram("go\n", cps);
7756         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7757     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7758         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7759             SendToProgram("go\n", cps);
7760         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7761     }
7762     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7763 }
7764
7765 char *savedMessage;
7766 ChessProgramState *savedState;
7767 void DeferredBookMove(void)
7768 {
7769         if(savedState->lastPing != savedState->lastPong)
7770                     ScheduleDelayedEvent(DeferredBookMove, 10);
7771         else
7772         HandleMachineMove(savedMessage, savedState);
7773 }
7774
7775 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7776
7777 void
7778 HandleMachineMove(message, cps)
7779      char *message;
7780      ChessProgramState *cps;
7781 {
7782     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7783     char realname[MSG_SIZ];
7784     int fromX, fromY, toX, toY;
7785     ChessMove moveType;
7786     char promoChar;
7787     char *p, *pv=buf1;
7788     int machineWhite;
7789     char *bookHit;
7790
7791     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7792         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7793         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7794             DisplayError(_("Invalid pairing from pairing engine"), 0);
7795             return;
7796         }
7797         pairingReceived = 1;
7798         NextMatchGame();
7799         return; // Skim the pairing messages here.
7800     }
7801
7802     cps->userError = 0;
7803
7804 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7805     /*
7806      * Kludge to ignore BEL characters
7807      */
7808     while (*message == '\007') message++;
7809
7810     /*
7811      * [HGM] engine debug message: ignore lines starting with '#' character
7812      */
7813     if(cps->debug && *message == '#') return;
7814
7815     /*
7816      * Look for book output
7817      */
7818     if (cps == &first && bookRequested) {
7819         if (message[0] == '\t' || message[0] == ' ') {
7820             /* Part of the book output is here; append it */
7821             strcat(bookOutput, message);
7822             strcat(bookOutput, "  \n");
7823             return;
7824         } else if (bookOutput[0] != NULLCHAR) {
7825             /* All of book output has arrived; display it */
7826             char *p = bookOutput;
7827             while (*p != NULLCHAR) {
7828                 if (*p == '\t') *p = ' ';
7829                 p++;
7830             }
7831             DisplayInformation(bookOutput);
7832             bookRequested = FALSE;
7833             /* Fall through to parse the current output */
7834         }
7835     }
7836
7837     /*
7838      * Look for machine move.
7839      */
7840     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7841         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7842     {
7843         /* This method is only useful on engines that support ping */
7844         if (cps->lastPing != cps->lastPong) {
7845           if (gameMode == BeginningOfGame) {
7846             /* Extra move from before last new; ignore */
7847             if (appData.debugMode) {
7848                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7849             }
7850           } else {
7851             if (appData.debugMode) {
7852                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7853                         cps->which, gameMode);
7854             }
7855
7856             SendToProgram("undo\n", cps);
7857           }
7858           return;
7859         }
7860
7861         switch (gameMode) {
7862           case BeginningOfGame:
7863             /* Extra move from before last reset; ignore */
7864             if (appData.debugMode) {
7865                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7866             }
7867             return;
7868
7869           case EndOfGame:
7870           case IcsIdle:
7871           default:
7872             /* Extra move after we tried to stop.  The mode test is
7873                not a reliable way of detecting this problem, but it's
7874                the best we can do on engines that don't support ping.
7875             */
7876             if (appData.debugMode) {
7877                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7878                         cps->which, gameMode);
7879             }
7880             SendToProgram("undo\n", cps);
7881             return;
7882
7883           case MachinePlaysWhite:
7884           case IcsPlayingWhite:
7885             machineWhite = TRUE;
7886             break;
7887
7888           case MachinePlaysBlack:
7889           case IcsPlayingBlack:
7890             machineWhite = FALSE;
7891             break;
7892
7893           case TwoMachinesPlay:
7894             machineWhite = (cps->twoMachinesColor[0] == 'w');
7895             break;
7896         }
7897         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7898             if (appData.debugMode) {
7899                 fprintf(debugFP,
7900                         "Ignoring move out of turn by %s, gameMode %d"
7901                         ", forwardMost %d\n",
7902                         cps->which, gameMode, forwardMostMove);
7903             }
7904             return;
7905         }
7906
7907     if (appData.debugMode) { int f = forwardMostMove;
7908         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7909                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7910                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7911     }
7912         if(cps->alphaRank) AlphaRank(machineMove, 4);
7913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7915             /* Machine move could not be parsed; ignore it. */
7916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7917                     machineMove, _(cps->which));
7918             DisplayError(buf1, 0);
7919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7921             if (gameMode == TwoMachinesPlay) {
7922               GameEnds(machineWhite ? BlackWins : WhiteWins,
7923                        buf1, GE_XBOARD);
7924             }
7925             return;
7926         }
7927
7928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7929         /* So we have to redo legality test with true e.p. status here,  */
7930         /* to make sure an illegal e.p. capture does not slip through,   */
7931         /* to cause a forfeit on a justified illegal-move complaint      */
7932         /* of the opponent.                                              */
7933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7934            ChessMove moveType;
7935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7936                              fromY, fromX, toY, toX, promoChar);
7937             if (appData.debugMode) {
7938                 int i;
7939                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7940                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7941                 fprintf(debugFP, "castling rights\n");
7942             }
7943             if(moveType == IllegalMove) {
7944               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7945                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7946                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7947                            buf1, GE_XBOARD);
7948                 return;
7949            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7950            /* [HGM] Kludge to handle engines that send FRC-style castling
7951               when they shouldn't (like TSCP-Gothic) */
7952            switch(moveType) {
7953              case WhiteASideCastleFR:
7954              case BlackASideCastleFR:
7955                toX+=2;
7956                currentMoveString[2]++;
7957                break;
7958              case WhiteHSideCastleFR:
7959              case BlackHSideCastleFR:
7960                toX--;
7961                currentMoveString[2]--;
7962                break;
7963              default: ; // nothing to do, but suppresses warning of pedantic compilers
7964            }
7965         }
7966         hintRequested = FALSE;
7967         lastHint[0] = NULLCHAR;
7968         bookRequested = FALSE;
7969         /* Program may be pondering now */
7970         cps->maybeThinking = TRUE;
7971         if (cps->sendTime == 2) cps->sendTime = 1;
7972         if (cps->offeredDraw) cps->offeredDraw--;
7973
7974         /* [AS] Save move info*/
7975         pvInfoList[ forwardMostMove ].score = programStats.score;
7976         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7977         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7978
7979         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7980
7981         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7982         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7983             int count = 0;
7984
7985             while( count < adjudicateLossPlies ) {
7986                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7987
7988                 if( count & 1 ) {
7989                     score = -score; /* Flip score for winning side */
7990                 }
7991
7992                 if( score > adjudicateLossThreshold ) {
7993                     break;
7994                 }
7995
7996                 count++;
7997             }
7998
7999             if( count >= adjudicateLossPlies ) {
8000                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8001
8002                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8003                     "Xboard adjudication",
8004                     GE_XBOARD );
8005
8006                 return;
8007             }
8008         }
8009
8010         if(Adjudicate(cps)) {
8011             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8012             return; // [HGM] adjudicate: for all automatic game ends
8013         }
8014
8015 #if ZIPPY
8016         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8017             first.initDone) {
8018           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8019                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8020                 SendToICS("draw ");
8021                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8022           }
8023           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8024           ics_user_moved = 1;
8025           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8026                 char buf[3*MSG_SIZ];
8027
8028                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8029                         programStats.score / 100.,
8030                         programStats.depth,
8031                         programStats.time / 100.,
8032                         (unsigned int)programStats.nodes,
8033                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8034                         programStats.movelist);
8035                 SendToICS(buf);
8036 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8037           }
8038         }
8039 #endif
8040
8041         /* [AS] Clear stats for next move */
8042         ClearProgramStats();
8043         thinkOutput[0] = NULLCHAR;
8044         hiddenThinkOutputState = 0;
8045
8046         bookHit = NULL;
8047         if (gameMode == TwoMachinesPlay) {
8048             /* [HGM] relaying draw offers moved to after reception of move */
8049             /* and interpreting offer as claim if it brings draw condition */
8050             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8051                 SendToProgram("draw\n", cps->other);
8052             }
8053             if (cps->other->sendTime) {
8054                 SendTimeRemaining(cps->other,
8055                                   cps->other->twoMachinesColor[0] == 'w');
8056             }
8057             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8058             if (firstMove && !bookHit) {
8059                 firstMove = FALSE;
8060                 if (cps->other->useColors) {
8061                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8062                 }
8063                 SendToProgram("go\n", cps->other);
8064             }
8065             cps->other->maybeThinking = TRUE;
8066         }
8067
8068         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8069
8070         if (!pausing && appData.ringBellAfterMoves) {
8071             RingBell();
8072         }
8073
8074         /*
8075          * Reenable menu items that were disabled while
8076          * machine was thinking
8077          */
8078         if (gameMode != TwoMachinesPlay)
8079             SetUserThinkingEnables();
8080
8081         // [HGM] book: after book hit opponent has received move and is now in force mode
8082         // force the book reply into it, and then fake that it outputted this move by jumping
8083         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8084         if(bookHit) {
8085                 static char bookMove[MSG_SIZ]; // a bit generous?
8086
8087                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8088                 strcat(bookMove, bookHit);
8089                 message = bookMove;
8090                 cps = cps->other;
8091                 programStats.nodes = programStats.depth = programStats.time =
8092                 programStats.score = programStats.got_only_move = 0;
8093                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8094
8095                 if(cps->lastPing != cps->lastPong) {
8096                     savedMessage = message; // args for deferred call
8097                     savedState = cps;
8098                     ScheduleDelayedEvent(DeferredBookMove, 10);
8099                     return;
8100                 }
8101                 goto FakeBookMove;
8102         }
8103
8104         return;
8105     }
8106
8107     /* Set special modes for chess engines.  Later something general
8108      *  could be added here; for now there is just one kludge feature,
8109      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8110      *  when "xboard" is given as an interactive command.
8111      */
8112     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8113         cps->useSigint = FALSE;
8114         cps->useSigterm = FALSE;
8115     }
8116     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8117       ParseFeatures(message+8, cps);
8118       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8119     }
8120
8121     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8122       int dummy, s=6; char buf[MSG_SIZ];
8123       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8124       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8125       ParseFEN(boards[0], &dummy, message+s);
8126       DrawPosition(TRUE, boards[0]);
8127       startedFromSetupPosition = TRUE;
8128       return;
8129     }
8130     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8131      * want this, I was asked to put it in, and obliged.
8132      */
8133     if (!strncmp(message, "setboard ", 9)) {
8134         Board initial_position;
8135
8136         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8137
8138         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8139             DisplayError(_("Bad FEN received from engine"), 0);
8140             return ;
8141         } else {
8142            Reset(TRUE, FALSE);
8143            CopyBoard(boards[0], initial_position);
8144            initialRulePlies = FENrulePlies;
8145            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8146            else gameMode = MachinePlaysBlack;
8147            DrawPosition(FALSE, boards[currentMove]);
8148         }
8149         return;
8150     }
8151
8152     /*
8153      * Look for communication commands
8154      */
8155     if (!strncmp(message, "telluser ", 9)) {
8156         if(message[9] == '\\' && message[10] == '\\')
8157             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8158         PlayTellSound();
8159         DisplayNote(message + 9);
8160         return;
8161     }
8162     if (!strncmp(message, "tellusererror ", 14)) {
8163         cps->userError = 1;
8164         if(message[14] == '\\' && message[15] == '\\')
8165             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8166         PlayTellSound();
8167         DisplayError(message + 14, 0);
8168         return;
8169     }
8170     if (!strncmp(message, "tellopponent ", 13)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 13);
8178       }
8179       return;
8180     }
8181     if (!strncmp(message, "tellothers ", 11)) {
8182       if (appData.icsActive) {
8183         if (loggedOn) {
8184           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8185           SendToICS(buf1);
8186         }
8187       }
8188       return;
8189     }
8190     if (!strncmp(message, "tellall ", 8)) {
8191       if (appData.icsActive) {
8192         if (loggedOn) {
8193           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8194           SendToICS(buf1);
8195         }
8196       } else {
8197         DisplayNote(message + 8);
8198       }
8199       return;
8200     }
8201     if (strncmp(message, "warning", 7) == 0) {
8202         /* Undocumented feature, use tellusererror in new code */
8203         DisplayError(message, 0);
8204         return;
8205     }
8206     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8207         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8208         strcat(realname, " query");
8209         AskQuestion(realname, buf2, buf1, cps->pr);
8210         return;
8211     }
8212     /* Commands from the engine directly to ICS.  We don't allow these to be
8213      *  sent until we are logged on. Crafty kibitzes have been known to
8214      *  interfere with the login process.
8215      */
8216     if (loggedOn) {
8217         if (!strncmp(message, "tellics ", 8)) {
8218             SendToICS(message + 8);
8219             SendToICS("\n");
8220             return;
8221         }
8222         if (!strncmp(message, "tellicsnoalias ", 15)) {
8223             SendToICS(ics_prefix);
8224             SendToICS(message + 15);
8225             SendToICS("\n");
8226             return;
8227         }
8228         /* The following are for backward compatibility only */
8229         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8230             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8231             SendToICS(ics_prefix);
8232             SendToICS(message);
8233             SendToICS("\n");
8234             return;
8235         }
8236     }
8237     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8238         return;
8239     }
8240     /*
8241      * If the move is illegal, cancel it and redraw the board.
8242      * Also deal with other error cases.  Matching is rather loose
8243      * here to accommodate engines written before the spec.
8244      */
8245     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8246         strncmp(message, "Error", 5) == 0) {
8247         if (StrStr(message, "name") ||
8248             StrStr(message, "rating") || StrStr(message, "?") ||
8249             StrStr(message, "result") || StrStr(message, "board") ||
8250             StrStr(message, "bk") || StrStr(message, "computer") ||
8251             StrStr(message, "variant") || StrStr(message, "hint") ||
8252             StrStr(message, "random") || StrStr(message, "depth") ||
8253             StrStr(message, "accepted")) {
8254             return;
8255         }
8256         if (StrStr(message, "protover")) {
8257           /* Program is responding to input, so it's apparently done
8258              initializing, and this error message indicates it is
8259              protocol version 1.  So we don't need to wait any longer
8260              for it to initialize and send feature commands. */
8261           FeatureDone(cps, 1);
8262           cps->protocolVersion = 1;
8263           return;
8264         }
8265         cps->maybeThinking = FALSE;
8266
8267         if (StrStr(message, "draw")) {
8268             /* Program doesn't have "draw" command */
8269             cps->sendDrawOffers = 0;
8270             return;
8271         }
8272         if (cps->sendTime != 1 &&
8273             (StrStr(message, "time") || StrStr(message, "otim"))) {
8274           /* Program apparently doesn't have "time" or "otim" command */
8275           cps->sendTime = 0;
8276           return;
8277         }
8278         if (StrStr(message, "analyze")) {
8279             cps->analysisSupport = FALSE;
8280             cps->analyzing = FALSE;
8281 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8282             EditGameEvent(); // [HGM] try to preserve loaded game
8283             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8284             DisplayError(buf2, 0);
8285             return;
8286         }
8287         if (StrStr(message, "(no matching move)st")) {
8288           /* Special kludge for GNU Chess 4 only */
8289           cps->stKludge = TRUE;
8290           SendTimeControl(cps, movesPerSession, timeControl,
8291                           timeIncrement, appData.searchDepth,
8292                           searchTime);
8293           return;
8294         }
8295         if (StrStr(message, "(no matching move)sd")) {
8296           /* Special kludge for GNU Chess 4 only */
8297           cps->sdKludge = TRUE;
8298           SendTimeControl(cps, movesPerSession, timeControl,
8299                           timeIncrement, appData.searchDepth,
8300                           searchTime);
8301           return;
8302         }
8303         if (!StrStr(message, "llegal")) {
8304             return;
8305         }
8306         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8307             gameMode == IcsIdle) return;
8308         if (forwardMostMove <= backwardMostMove) return;
8309         if (pausing) PauseEvent();
8310       if(appData.forceIllegal) {
8311             // [HGM] illegal: machine refused move; force position after move into it
8312           SendToProgram("force\n", cps);
8313           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8314                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8315                 // when black is to move, while there might be nothing on a2 or black
8316                 // might already have the move. So send the board as if white has the move.
8317                 // But first we must change the stm of the engine, as it refused the last move
8318                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8319                 if(WhiteOnMove(forwardMostMove)) {
8320                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8321                     SendBoard(cps, forwardMostMove); // kludgeless board
8322                 } else {
8323                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8324                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8325                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8326                 }
8327           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8328             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8329                  gameMode == TwoMachinesPlay)
8330               SendToProgram("go\n", cps);
8331             return;
8332       } else
8333         if (gameMode == PlayFromGameFile) {
8334             /* Stop reading this game file */
8335             gameMode = EditGame;
8336             ModeHighlight();
8337         }
8338         /* [HGM] illegal-move claim should forfeit game when Xboard */
8339         /* only passes fully legal moves                            */
8340         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8341             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8342                                 "False illegal-move claim", GE_XBOARD );
8343             return; // do not take back move we tested as valid
8344         }
8345         currentMove = forwardMostMove-1;
8346         DisplayMove(currentMove-1); /* before DisplayMoveError */
8347         SwitchClocks(forwardMostMove-1); // [HGM] race
8348         DisplayBothClocks();
8349         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8350                 parseList[currentMove], _(cps->which));
8351         DisplayMoveError(buf1);
8352         DrawPosition(FALSE, boards[currentMove]);
8353         return;
8354     }
8355     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8356         /* Program has a broken "time" command that
8357            outputs a string not ending in newline.
8358            Don't use it. */
8359         cps->sendTime = 0;
8360     }
8361
8362     /*
8363      * If chess program startup fails, exit with an error message.
8364      * Attempts to recover here are futile.
8365      */
8366     if ((StrStr(message, "unknown host") != NULL)
8367         || (StrStr(message, "No remote directory") != NULL)
8368         || (StrStr(message, "not found") != NULL)
8369         || (StrStr(message, "No such file") != NULL)
8370         || (StrStr(message, "can't alloc") != NULL)
8371         || (StrStr(message, "Permission denied") != NULL)) {
8372
8373         cps->maybeThinking = FALSE;
8374         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8375                 _(cps->which), cps->program, cps->host, message);
8376         RemoveInputSource(cps->isr);
8377         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8378             if(cps == &first) appData.noChessProgram = TRUE;
8379             DisplayError(buf1, 0);
8380         }
8381         return;
8382     }
8383
8384     /*
8385      * Look for hint output
8386      */
8387     if (sscanf(message, "Hint: %s", buf1) == 1) {
8388         if (cps == &first && hintRequested) {
8389             hintRequested = FALSE;
8390             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8391                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8392                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8393                                     PosFlags(forwardMostMove),
8394                                     fromY, fromX, toY, toX, promoChar, buf1);
8395                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8396                 DisplayInformation(buf2);
8397             } else {
8398                 /* Hint move could not be parsed!? */
8399               snprintf(buf2, sizeof(buf2),
8400                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8401                         buf1, _(cps->which));
8402                 DisplayError(buf2, 0);
8403             }
8404         } else {
8405           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8406         }
8407         return;
8408     }
8409
8410     /*
8411      * Ignore other messages if game is not in progress
8412      */
8413     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8414         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8415
8416     /*
8417      * look for win, lose, draw, or draw offer
8418      */
8419     if (strncmp(message, "1-0", 3) == 0) {
8420         char *p, *q, *r = "";
8421         p = strchr(message, '{');
8422         if (p) {
8423             q = strchr(p, '}');
8424             if (q) {
8425                 *q = NULLCHAR;
8426                 r = p + 1;
8427             }
8428         }
8429         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8430         return;
8431     } else if (strncmp(message, "0-1", 3) == 0) {
8432         char *p, *q, *r = "";
8433         p = strchr(message, '{');
8434         if (p) {
8435             q = strchr(p, '}');
8436             if (q) {
8437                 *q = NULLCHAR;
8438                 r = p + 1;
8439             }
8440         }
8441         /* Kludge for Arasan 4.1 bug */
8442         if (strcmp(r, "Black resigns") == 0) {
8443             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8444             return;
8445         }
8446         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8447         return;
8448     } else if (strncmp(message, "1/2", 3) == 0) {
8449         char *p, *q, *r = "";
8450         p = strchr(message, '{');
8451         if (p) {
8452             q = strchr(p, '}');
8453             if (q) {
8454                 *q = NULLCHAR;
8455                 r = p + 1;
8456             }
8457         }
8458
8459         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8460         return;
8461
8462     } else if (strncmp(message, "White resign", 12) == 0) {
8463         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8464         return;
8465     } else if (strncmp(message, "Black resign", 12) == 0) {
8466         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8467         return;
8468     } else if (strncmp(message, "White matches", 13) == 0 ||
8469                strncmp(message, "Black matches", 13) == 0   ) {
8470         /* [HGM] ignore GNUShogi noises */
8471         return;
8472     } else if (strncmp(message, "White", 5) == 0 &&
8473                message[5] != '(' &&
8474                StrStr(message, "Black") == NULL) {
8475         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8476         return;
8477     } else if (strncmp(message, "Black", 5) == 0 &&
8478                message[5] != '(') {
8479         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8480         return;
8481     } else if (strcmp(message, "resign") == 0 ||
8482                strcmp(message, "computer resigns") == 0) {
8483         switch (gameMode) {
8484           case MachinePlaysBlack:
8485           case IcsPlayingBlack:
8486             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8487             break;
8488           case MachinePlaysWhite:
8489           case IcsPlayingWhite:
8490             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8491             break;
8492           case TwoMachinesPlay:
8493             if (cps->twoMachinesColor[0] == 'w')
8494               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8495             else
8496               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8497             break;
8498           default:
8499             /* can't happen */
8500             break;
8501         }
8502         return;
8503     } else if (strncmp(message, "opponent mates", 14) == 0) {
8504         switch (gameMode) {
8505           case MachinePlaysBlack:
8506           case IcsPlayingBlack:
8507             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8508             break;
8509           case MachinePlaysWhite:
8510           case IcsPlayingWhite:
8511             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8512             break;
8513           case TwoMachinesPlay:
8514             if (cps->twoMachinesColor[0] == 'w')
8515               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8516             else
8517               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8518             break;
8519           default:
8520             /* can't happen */
8521             break;
8522         }
8523         return;
8524     } else if (strncmp(message, "computer mates", 14) == 0) {
8525         switch (gameMode) {
8526           case MachinePlaysBlack:
8527           case IcsPlayingBlack:
8528             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8529             break;
8530           case MachinePlaysWhite:
8531           case IcsPlayingWhite:
8532             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8533             break;
8534           case TwoMachinesPlay:
8535             if (cps->twoMachinesColor[0] == 'w')
8536               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8537             else
8538               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8539             break;
8540           default:
8541             /* can't happen */
8542             break;
8543         }
8544         return;
8545     } else if (strncmp(message, "checkmate", 9) == 0) {
8546         if (WhiteOnMove(forwardMostMove)) {
8547             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8548         } else {
8549             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8550         }
8551         return;
8552     } else if (strstr(message, "Draw") != NULL ||
8553                strstr(message, "game is a draw") != NULL) {
8554         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8555         return;
8556     } else if (strstr(message, "offer") != NULL &&
8557                strstr(message, "draw") != NULL) {
8558 #if ZIPPY
8559         if (appData.zippyPlay && first.initDone) {
8560             /* Relay offer to ICS */
8561             SendToICS(ics_prefix);
8562             SendToICS("draw\n");
8563         }
8564 #endif
8565         cps->offeredDraw = 2; /* valid until this engine moves twice */
8566         if (gameMode == TwoMachinesPlay) {
8567             if (cps->other->offeredDraw) {
8568                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8569             /* [HGM] in two-machine mode we delay relaying draw offer      */
8570             /* until after we also have move, to see if it is really claim */
8571             }
8572         } else if (gameMode == MachinePlaysWhite ||
8573                    gameMode == MachinePlaysBlack) {
8574           if (userOfferedDraw) {
8575             DisplayInformation(_("Machine accepts your draw offer"));
8576             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8577           } else {
8578             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8579           }
8580         }
8581     }
8582
8583
8584     /*
8585      * Look for thinking output
8586      */
8587     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8588           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8589                                 ) {
8590         int plylev, mvleft, mvtot, curscore, time;
8591         char mvname[MOVE_LEN];
8592         u64 nodes; // [DM]
8593         char plyext;
8594         int ignore = FALSE;
8595         int prefixHint = FALSE;
8596         mvname[0] = NULLCHAR;
8597
8598         switch (gameMode) {
8599           case MachinePlaysBlack:
8600           case IcsPlayingBlack:
8601             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8602             break;
8603           case MachinePlaysWhite:
8604           case IcsPlayingWhite:
8605             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8606             break;
8607           case AnalyzeMode:
8608           case AnalyzeFile:
8609             break;
8610           case IcsObserving: /* [DM] icsEngineAnalyze */
8611             if (!appData.icsEngineAnalyze) ignore = TRUE;
8612             break;
8613           case TwoMachinesPlay:
8614             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8615                 ignore = TRUE;
8616             }
8617             break;
8618           default:
8619             ignore = TRUE;
8620             break;
8621         }
8622
8623         if (!ignore) {
8624             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8625             buf1[0] = NULLCHAR;
8626             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8627                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8628
8629                 if (plyext != ' ' && plyext != '\t') {
8630                     time *= 100;
8631                 }
8632
8633                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8634                 if( cps->scoreIsAbsolute &&
8635                     ( gameMode == MachinePlaysBlack ||
8636                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8637                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8638                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8639                      !WhiteOnMove(currentMove)
8640                     ) )
8641                 {
8642                     curscore = -curscore;
8643                 }
8644
8645                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8646
8647                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8648                         char buf[MSG_SIZ];
8649                         FILE *f;
8650                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8651                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8652                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8653                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8654                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8655                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8656                                 fclose(f);
8657                         } else DisplayError("failed writing PV", 0);
8658                 }
8659
8660                 tempStats.depth = plylev;
8661                 tempStats.nodes = nodes;
8662                 tempStats.time = time;
8663                 tempStats.score = curscore;
8664                 tempStats.got_only_move = 0;
8665
8666                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8667                         int ticklen;
8668
8669                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8670                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8671                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8672                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8673                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8674                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8675                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8676                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8677                 }
8678
8679                 /* Buffer overflow protection */
8680                 if (pv[0] != NULLCHAR) {
8681                     if (strlen(pv) >= sizeof(tempStats.movelist)
8682                         && appData.debugMode) {
8683                         fprintf(debugFP,
8684                                 "PV is too long; using the first %u bytes.\n",
8685                                 (unsigned) sizeof(tempStats.movelist) - 1);
8686                     }
8687
8688                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8689                 } else {
8690                     sprintf(tempStats.movelist, " no PV\n");
8691                 }
8692
8693                 if (tempStats.seen_stat) {
8694                     tempStats.ok_to_send = 1;
8695                 }
8696
8697                 if (strchr(tempStats.movelist, '(') != NULL) {
8698                     tempStats.line_is_book = 1;
8699                     tempStats.nr_moves = 0;
8700                     tempStats.moves_left = 0;
8701                 } else {
8702                     tempStats.line_is_book = 0;
8703                 }
8704
8705                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8706                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8707
8708                 SendProgramStatsToFrontend( cps, &tempStats );
8709
8710                 /*
8711                     [AS] Protect the thinkOutput buffer from overflow... this
8712                     is only useful if buf1 hasn't overflowed first!
8713                 */
8714                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8715                          plylev,
8716                          (gameMode == TwoMachinesPlay ?
8717                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8718                          ((double) curscore) / 100.0,
8719                          prefixHint ? lastHint : "",
8720                          prefixHint ? " " : "" );
8721
8722                 if( buf1[0] != NULLCHAR ) {
8723                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8724
8725                     if( strlen(pv) > max_len ) {
8726                         if( appData.debugMode) {
8727                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8728                         }
8729                         pv[max_len+1] = '\0';
8730                     }
8731
8732                     strcat( thinkOutput, pv);
8733                 }
8734
8735                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8736                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8737                     DisplayMove(currentMove - 1);
8738                 }
8739                 return;
8740
8741             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8742                 /* crafty (9.25+) says "(only move) <move>"
8743                  * if there is only 1 legal move
8744                  */
8745                 sscanf(p, "(only move) %s", buf1);
8746                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8747                 sprintf(programStats.movelist, "%s (only move)", buf1);
8748                 programStats.depth = 1;
8749                 programStats.nr_moves = 1;
8750                 programStats.moves_left = 1;
8751                 programStats.nodes = 1;
8752                 programStats.time = 1;
8753                 programStats.got_only_move = 1;
8754
8755                 /* Not really, but we also use this member to
8756                    mean "line isn't going to change" (Crafty
8757                    isn't searching, so stats won't change) */
8758                 programStats.line_is_book = 1;
8759
8760                 SendProgramStatsToFrontend( cps, &programStats );
8761
8762                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8763                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8764                     DisplayMove(currentMove - 1);
8765                 }
8766                 return;
8767             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8768                               &time, &nodes, &plylev, &mvleft,
8769                               &mvtot, mvname) >= 5) {
8770                 /* The stat01: line is from Crafty (9.29+) in response
8771                    to the "." command */
8772                 programStats.seen_stat = 1;
8773                 cps->maybeThinking = TRUE;
8774
8775                 if (programStats.got_only_move || !appData.periodicUpdates)
8776                   return;
8777
8778                 programStats.depth = plylev;
8779                 programStats.time = time;
8780                 programStats.nodes = nodes;
8781                 programStats.moves_left = mvleft;
8782                 programStats.nr_moves = mvtot;
8783                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8784                 programStats.ok_to_send = 1;
8785                 programStats.movelist[0] = '\0';
8786
8787                 SendProgramStatsToFrontend( cps, &programStats );
8788
8789                 return;
8790
8791             } else if (strncmp(message,"++",2) == 0) {
8792                 /* Crafty 9.29+ outputs this */
8793                 programStats.got_fail = 2;
8794                 return;
8795
8796             } else if (strncmp(message,"--",2) == 0) {
8797                 /* Crafty 9.29+ outputs this */
8798                 programStats.got_fail = 1;
8799                 return;
8800
8801             } else if (thinkOutput[0] != NULLCHAR &&
8802                        strncmp(message, "    ", 4) == 0) {
8803                 unsigned message_len;
8804
8805                 p = message;
8806                 while (*p && *p == ' ') p++;
8807
8808                 message_len = strlen( p );
8809
8810                 /* [AS] Avoid buffer overflow */
8811                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8812                     strcat(thinkOutput, " ");
8813                     strcat(thinkOutput, p);
8814                 }
8815
8816                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8817                     strcat(programStats.movelist, " ");
8818                     strcat(programStats.movelist, p);
8819                 }
8820
8821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8823                     DisplayMove(currentMove - 1);
8824                 }
8825                 return;
8826             }
8827         }
8828         else {
8829             buf1[0] = NULLCHAR;
8830
8831             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8832                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8833             {
8834                 ChessProgramStats cpstats;
8835
8836                 if (plyext != ' ' && plyext != '\t') {
8837                     time *= 100;
8838                 }
8839
8840                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8841                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8842                     curscore = -curscore;
8843                 }
8844
8845                 cpstats.depth = plylev;
8846                 cpstats.nodes = nodes;
8847                 cpstats.time = time;
8848                 cpstats.score = curscore;
8849                 cpstats.got_only_move = 0;
8850                 cpstats.movelist[0] = '\0';
8851
8852                 if (buf1[0] != NULLCHAR) {
8853                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8854                 }
8855
8856                 cpstats.ok_to_send = 0;
8857                 cpstats.line_is_book = 0;
8858                 cpstats.nr_moves = 0;
8859                 cpstats.moves_left = 0;
8860
8861                 SendProgramStatsToFrontend( cps, &cpstats );
8862             }
8863         }
8864     }
8865 }
8866
8867
8868 /* Parse a game score from the character string "game", and
8869    record it as the history of the current game.  The game
8870    score is NOT assumed to start from the standard position.
8871    The display is not updated in any way.
8872    */
8873 void
8874 ParseGameHistory(game)
8875      char *game;
8876 {
8877     ChessMove moveType;
8878     int fromX, fromY, toX, toY, boardIndex;
8879     char promoChar;
8880     char *p, *q;
8881     char buf[MSG_SIZ];
8882
8883     if (appData.debugMode)
8884       fprintf(debugFP, "Parsing game history: %s\n", game);
8885
8886     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8887     gameInfo.site = StrSave(appData.icsHost);
8888     gameInfo.date = PGNDate();
8889     gameInfo.round = StrSave("-");
8890
8891     /* Parse out names of players */
8892     while (*game == ' ') game++;
8893     p = buf;
8894     while (*game != ' ') *p++ = *game++;
8895     *p = NULLCHAR;
8896     gameInfo.white = StrSave(buf);
8897     while (*game == ' ') game++;
8898     p = buf;
8899     while (*game != ' ' && *game != '\n') *p++ = *game++;
8900     *p = NULLCHAR;
8901     gameInfo.black = StrSave(buf);
8902
8903     /* Parse moves */
8904     boardIndex = blackPlaysFirst ? 1 : 0;
8905     yynewstr(game);
8906     for (;;) {
8907         yyboardindex = boardIndex;
8908         moveType = (ChessMove) Myylex();
8909         switch (moveType) {
8910           case IllegalMove:             /* maybe suicide chess, etc. */
8911   if (appData.debugMode) {
8912     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8913     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8914     setbuf(debugFP, NULL);
8915   }
8916           case WhitePromotion:
8917           case BlackPromotion:
8918           case WhiteNonPromotion:
8919           case BlackNonPromotion:
8920           case NormalMove:
8921           case WhiteCapturesEnPassant:
8922           case BlackCapturesEnPassant:
8923           case WhiteKingSideCastle:
8924           case WhiteQueenSideCastle:
8925           case BlackKingSideCastle:
8926           case BlackQueenSideCastle:
8927           case WhiteKingSideCastleWild:
8928           case WhiteQueenSideCastleWild:
8929           case BlackKingSideCastleWild:
8930           case BlackQueenSideCastleWild:
8931           /* PUSH Fabien */
8932           case WhiteHSideCastleFR:
8933           case WhiteASideCastleFR:
8934           case BlackHSideCastleFR:
8935           case BlackASideCastleFR:
8936           /* POP Fabien */
8937             fromX = currentMoveString[0] - AAA;
8938             fromY = currentMoveString[1] - ONE;
8939             toX = currentMoveString[2] - AAA;
8940             toY = currentMoveString[3] - ONE;
8941             promoChar = currentMoveString[4];
8942             break;
8943           case WhiteDrop:
8944           case BlackDrop:
8945             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8946             fromX = moveType == WhiteDrop ?
8947               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8948             (int) CharToPiece(ToLower(currentMoveString[0]));
8949             fromY = DROP_RANK;
8950             toX = currentMoveString[2] - AAA;
8951             toY = currentMoveString[3] - ONE;
8952             promoChar = NULLCHAR;
8953             break;
8954           case AmbiguousMove:
8955             /* bug? */
8956             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8957   if (appData.debugMode) {
8958     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8959     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8960     setbuf(debugFP, NULL);
8961   }
8962             DisplayError(buf, 0);
8963             return;
8964           case ImpossibleMove:
8965             /* bug? */
8966             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8967   if (appData.debugMode) {
8968     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8969     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8970     setbuf(debugFP, NULL);
8971   }
8972             DisplayError(buf, 0);
8973             return;
8974           case EndOfFile:
8975             if (boardIndex < backwardMostMove) {
8976                 /* Oops, gap.  How did that happen? */
8977                 DisplayError(_("Gap in move list"), 0);
8978                 return;
8979             }
8980             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8981             if (boardIndex > forwardMostMove) {
8982                 forwardMostMove = boardIndex;
8983             }
8984             return;
8985           case ElapsedTime:
8986             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8987                 strcat(parseList[boardIndex-1], " ");
8988                 strcat(parseList[boardIndex-1], yy_text);
8989             }
8990             continue;
8991           case Comment:
8992           case PGNTag:
8993           case NAG:
8994           default:
8995             /* ignore */
8996             continue;
8997           case WhiteWins:
8998           case BlackWins:
8999           case GameIsDrawn:
9000           case GameUnfinished:
9001             if (gameMode == IcsExamining) {
9002                 if (boardIndex < backwardMostMove) {
9003                     /* Oops, gap.  How did that happen? */
9004                     return;
9005                 }
9006                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9007                 return;
9008             }
9009             gameInfo.result = moveType;
9010             p = strchr(yy_text, '{');
9011             if (p == NULL) p = strchr(yy_text, '(');
9012             if (p == NULL) {
9013                 p = yy_text;
9014                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9015             } else {
9016                 q = strchr(p, *p == '{' ? '}' : ')');
9017                 if (q != NULL) *q = NULLCHAR;
9018                 p++;
9019             }
9020             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9021             gameInfo.resultDetails = StrSave(p);
9022             continue;
9023         }
9024         if (boardIndex >= forwardMostMove &&
9025             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9026             backwardMostMove = blackPlaysFirst ? 1 : 0;
9027             return;
9028         }
9029         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9030                                  fromY, fromX, toY, toX, promoChar,
9031                                  parseList[boardIndex]);
9032         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9033         /* currentMoveString is set as a side-effect of yylex */
9034         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9035         strcat(moveList[boardIndex], "\n");
9036         boardIndex++;
9037         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9038         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9039           case MT_NONE:
9040           case MT_STALEMATE:
9041           default:
9042             break;
9043           case MT_CHECK:
9044             if(gameInfo.variant != VariantShogi)
9045                 strcat(parseList[boardIndex - 1], "+");
9046             break;
9047           case MT_CHECKMATE:
9048           case MT_STAINMATE:
9049             strcat(parseList[boardIndex - 1], "#");
9050             break;
9051         }
9052     }
9053 }
9054
9055
9056 /* Apply a move to the given board  */
9057 void
9058 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9059      int fromX, fromY, toX, toY;
9060      int promoChar;
9061      Board board;
9062 {
9063   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9064   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9065
9066     /* [HGM] compute & store e.p. status and castling rights for new position */
9067     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9068
9069       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9070       oldEP = (signed char)board[EP_STATUS];
9071       board[EP_STATUS] = EP_NONE;
9072
9073   if (fromY == DROP_RANK) {
9074         /* must be first */
9075         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9076             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9077             return;
9078         }
9079         piece = board[toY][toX] = (ChessSquare) fromX;
9080   } else {
9081       int i;
9082
9083       if( board[toY][toX] != EmptySquare )
9084            board[EP_STATUS] = EP_CAPTURE;
9085
9086       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9087            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9088                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9089       } else
9090       if( board[fromY][fromX] == WhitePawn ) {
9091            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9092                board[EP_STATUS] = EP_PAWN_MOVE;
9093            if( toY-fromY==2) {
9094                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9095                         gameInfo.variant != VariantBerolina || toX < fromX)
9096                       board[EP_STATUS] = toX | berolina;
9097                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9098                         gameInfo.variant != VariantBerolina || toX > fromX)
9099                       board[EP_STATUS] = toX;
9100            }
9101       } else
9102       if( board[fromY][fromX] == BlackPawn ) {
9103            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9104                board[EP_STATUS] = EP_PAWN_MOVE;
9105            if( toY-fromY== -2) {
9106                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9107                         gameInfo.variant != VariantBerolina || toX < fromX)
9108                       board[EP_STATUS] = toX | berolina;
9109                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9110                         gameInfo.variant != VariantBerolina || toX > fromX)
9111                       board[EP_STATUS] = toX;
9112            }
9113        }
9114
9115        for(i=0; i<nrCastlingRights; i++) {
9116            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9117               board[CASTLING][i] == toX   && castlingRank[i] == toY
9118              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9119        }
9120
9121      if (fromX == toX && fromY == toY) return;
9122
9123      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9124      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9125      if(gameInfo.variant == VariantKnightmate)
9126          king += (int) WhiteUnicorn - (int) WhiteKing;
9127
9128     /* Code added by Tord: */
9129     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9130     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9131         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9132       board[fromY][fromX] = EmptySquare;
9133       board[toY][toX] = EmptySquare;
9134       if((toX > fromX) != (piece == WhiteRook)) {
9135         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9136       } else {
9137         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9138       }
9139     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9140                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9141       board[fromY][fromX] = EmptySquare;
9142       board[toY][toX] = EmptySquare;
9143       if((toX > fromX) != (piece == BlackRook)) {
9144         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9145       } else {
9146         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9147       }
9148     /* End of code added by Tord */
9149
9150     } else if (board[fromY][fromX] == king
9151         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9152         && toY == fromY && toX > fromX+1) {
9153         board[fromY][fromX] = EmptySquare;
9154         board[toY][toX] = king;
9155         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9156         board[fromY][BOARD_RGHT-1] = EmptySquare;
9157     } else if (board[fromY][fromX] == king
9158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9159                && toY == fromY && toX < fromX-1) {
9160         board[fromY][fromX] = EmptySquare;
9161         board[toY][toX] = king;
9162         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9163         board[fromY][BOARD_LEFT] = EmptySquare;
9164     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9165                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9166                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9167                ) {
9168         /* white pawn promotion */
9169         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9170         if(gameInfo.variant==VariantBughouse ||
9171            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9172             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9173         board[fromY][fromX] = EmptySquare;
9174     } else if ((fromY >= BOARD_HEIGHT>>1)
9175                && (toX != fromX)
9176                && gameInfo.variant != VariantXiangqi
9177                && gameInfo.variant != VariantBerolina
9178                && (board[fromY][fromX] == WhitePawn)
9179                && (board[toY][toX] == EmptySquare)) {
9180         board[fromY][fromX] = EmptySquare;
9181         board[toY][toX] = WhitePawn;
9182         captured = board[toY - 1][toX];
9183         board[toY - 1][toX] = EmptySquare;
9184     } else if ((fromY == BOARD_HEIGHT-4)
9185                && (toX == fromX)
9186                && gameInfo.variant == VariantBerolina
9187                && (board[fromY][fromX] == WhitePawn)
9188                && (board[toY][toX] == EmptySquare)) {
9189         board[fromY][fromX] = EmptySquare;
9190         board[toY][toX] = WhitePawn;
9191         if(oldEP & EP_BEROLIN_A) {
9192                 captured = board[fromY][fromX-1];
9193                 board[fromY][fromX-1] = EmptySquare;
9194         }else{  captured = board[fromY][fromX+1];
9195                 board[fromY][fromX+1] = EmptySquare;
9196         }
9197     } else if (board[fromY][fromX] == king
9198         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9199                && toY == fromY && toX > fromX+1) {
9200         board[fromY][fromX] = EmptySquare;
9201         board[toY][toX] = king;
9202         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9203         board[fromY][BOARD_RGHT-1] = EmptySquare;
9204     } else if (board[fromY][fromX] == king
9205         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9206                && toY == fromY && toX < fromX-1) {
9207         board[fromY][fromX] = EmptySquare;
9208         board[toY][toX] = king;
9209         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9210         board[fromY][BOARD_LEFT] = EmptySquare;
9211     } else if (fromY == 7 && fromX == 3
9212                && board[fromY][fromX] == BlackKing
9213                && toY == 7 && toX == 5) {
9214         board[fromY][fromX] = EmptySquare;
9215         board[toY][toX] = BlackKing;
9216         board[fromY][7] = EmptySquare;
9217         board[toY][4] = BlackRook;
9218     } else if (fromY == 7 && fromX == 3
9219                && board[fromY][fromX] == BlackKing
9220                && toY == 7 && toX == 1) {
9221         board[fromY][fromX] = EmptySquare;
9222         board[toY][toX] = BlackKing;
9223         board[fromY][0] = EmptySquare;
9224         board[toY][2] = BlackRook;
9225     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9226                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9227                && toY < promoRank && promoChar
9228                ) {
9229         /* black pawn promotion */
9230         board[toY][toX] = CharToPiece(ToLower(promoChar));
9231         if(gameInfo.variant==VariantBughouse ||
9232            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9233             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9234         board[fromY][fromX] = EmptySquare;
9235     } else if ((fromY < BOARD_HEIGHT>>1)
9236                && (toX != fromX)
9237                && gameInfo.variant != VariantXiangqi
9238                && gameInfo.variant != VariantBerolina
9239                && (board[fromY][fromX] == BlackPawn)
9240                && (board[toY][toX] == EmptySquare)) {
9241         board[fromY][fromX] = EmptySquare;
9242         board[toY][toX] = BlackPawn;
9243         captured = board[toY + 1][toX];
9244         board[toY + 1][toX] = EmptySquare;
9245     } else if ((fromY == 3)
9246                && (toX == fromX)
9247                && gameInfo.variant == VariantBerolina
9248                && (board[fromY][fromX] == BlackPawn)
9249                && (board[toY][toX] == EmptySquare)) {
9250         board[fromY][fromX] = EmptySquare;
9251         board[toY][toX] = BlackPawn;
9252         if(oldEP & EP_BEROLIN_A) {
9253                 captured = board[fromY][fromX-1];
9254                 board[fromY][fromX-1] = EmptySquare;
9255         }else{  captured = board[fromY][fromX+1];
9256                 board[fromY][fromX+1] = EmptySquare;
9257         }
9258     } else {
9259         board[toY][toX] = board[fromY][fromX];
9260         board[fromY][fromX] = EmptySquare;
9261     }
9262   }
9263
9264     if (gameInfo.holdingsWidth != 0) {
9265
9266       /* !!A lot more code needs to be written to support holdings  */
9267       /* [HGM] OK, so I have written it. Holdings are stored in the */
9268       /* penultimate board files, so they are automaticlly stored   */
9269       /* in the game history.                                       */
9270       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9271                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9272         /* Delete from holdings, by decreasing count */
9273         /* and erasing image if necessary            */
9274         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9275         if(p < (int) BlackPawn) { /* white drop */
9276              p -= (int)WhitePawn;
9277                  p = PieceToNumber((ChessSquare)p);
9278              if(p >= gameInfo.holdingsSize) p = 0;
9279              if(--board[p][BOARD_WIDTH-2] <= 0)
9280                   board[p][BOARD_WIDTH-1] = EmptySquare;
9281              if((int)board[p][BOARD_WIDTH-2] < 0)
9282                         board[p][BOARD_WIDTH-2] = 0;
9283         } else {                  /* black drop */
9284              p -= (int)BlackPawn;
9285                  p = PieceToNumber((ChessSquare)p);
9286              if(p >= gameInfo.holdingsSize) p = 0;
9287              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9288                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9289              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9290                         board[BOARD_HEIGHT-1-p][1] = 0;
9291         }
9292       }
9293       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9294           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9295         /* [HGM] holdings: Add to holdings, if holdings exist */
9296         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9297                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9298                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9299         }
9300         p = (int) captured;
9301         if (p >= (int) BlackPawn) {
9302           p -= (int)BlackPawn;
9303           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9304                   /* in Shogi restore piece to its original  first */
9305                   captured = (ChessSquare) (DEMOTED captured);
9306                   p = DEMOTED p;
9307           }
9308           p = PieceToNumber((ChessSquare)p);
9309           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9310           board[p][BOARD_WIDTH-2]++;
9311           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9312         } else {
9313           p -= (int)WhitePawn;
9314           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9315                   captured = (ChessSquare) (DEMOTED captured);
9316                   p = DEMOTED p;
9317           }
9318           p = PieceToNumber((ChessSquare)p);
9319           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9320           board[BOARD_HEIGHT-1-p][1]++;
9321           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9322         }
9323       }
9324     } else if (gameInfo.variant == VariantAtomic) {
9325       if (captured != EmptySquare) {
9326         int y, x;
9327         for (y = toY-1; y <= toY+1; y++) {
9328           for (x = toX-1; x <= toX+1; x++) {
9329             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9330                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9331               board[y][x] = EmptySquare;
9332             }
9333           }
9334         }
9335         board[toY][toX] = EmptySquare;
9336       }
9337     }
9338     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9339         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9340     } else
9341     if(promoChar == '+') {
9342         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9343         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9344     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9345         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9346     }
9347     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9348                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9349         // [HGM] superchess: take promotion piece out of holdings
9350         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9351         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9352             if(!--board[k][BOARD_WIDTH-2])
9353                 board[k][BOARD_WIDTH-1] = EmptySquare;
9354         } else {
9355             if(!--board[BOARD_HEIGHT-1-k][1])
9356                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9357         }
9358     }
9359
9360 }
9361
9362 /* Updates forwardMostMove */
9363 void
9364 MakeMove(fromX, fromY, toX, toY, promoChar)
9365      int fromX, fromY, toX, toY;
9366      int promoChar;
9367 {
9368 //    forwardMostMove++; // [HGM] bare: moved downstream
9369
9370     (void) CoordsToAlgebraic(boards[forwardMostMove],
9371                              PosFlags(forwardMostMove),
9372                              fromY, fromX, toY, toX, promoChar,
9373                              parseList[forwardMostMove]);
9374
9375     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9376         int timeLeft; static int lastLoadFlag=0; int king, piece;
9377         piece = boards[forwardMostMove][fromY][fromX];
9378         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9379         if(gameInfo.variant == VariantKnightmate)
9380             king += (int) WhiteUnicorn - (int) WhiteKing;
9381         if(forwardMostMove == 0) {
9382             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9383                 fprintf(serverMoves, "%s;", UserName());
9384             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9385                 fprintf(serverMoves, "%s;", second.tidy);
9386             fprintf(serverMoves, "%s;", first.tidy);
9387             if(gameMode == MachinePlaysWhite)
9388                 fprintf(serverMoves, "%s;", UserName());
9389             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9390                 fprintf(serverMoves, "%s;", second.tidy);
9391         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9392         lastLoadFlag = loadFlag;
9393         // print base move
9394         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9395         // print castling suffix
9396         if( toY == fromY && piece == king ) {
9397             if(toX-fromX > 1)
9398                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9399             if(fromX-toX >1)
9400                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9401         }
9402         // e.p. suffix
9403         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9404              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9405              boards[forwardMostMove][toY][toX] == EmptySquare
9406              && fromX != toX && fromY != toY)
9407                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9408         // promotion suffix
9409         if(promoChar != NULLCHAR)
9410                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9411         if(!loadFlag) {
9412                 char buf[MOVE_LEN*2], *p; int len;
9413             fprintf(serverMoves, "/%d/%d",
9414                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9415             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9416             else                      timeLeft = blackTimeRemaining/1000;
9417             fprintf(serverMoves, "/%d", timeLeft);
9418                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9419                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9420                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9421             fprintf(serverMoves, "/%s", buf);
9422         }
9423         fflush(serverMoves);
9424     }
9425
9426     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9427         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9428       return;
9429     }
9430     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9431     if (commentList[forwardMostMove+1] != NULL) {
9432         free(commentList[forwardMostMove+1]);
9433         commentList[forwardMostMove+1] = NULL;
9434     }
9435     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9436     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9437     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9438     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9439     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9440     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9441     gameInfo.result = GameUnfinished;
9442     if (gameInfo.resultDetails != NULL) {
9443         free(gameInfo.resultDetails);
9444         gameInfo.resultDetails = NULL;
9445     }
9446     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9447                               moveList[forwardMostMove - 1]);
9448     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9449       case MT_NONE:
9450       case MT_STALEMATE:
9451       default:
9452         break;
9453       case MT_CHECK:
9454         if(gameInfo.variant != VariantShogi)
9455             strcat(parseList[forwardMostMove - 1], "+");
9456         break;
9457       case MT_CHECKMATE:
9458       case MT_STAINMATE:
9459         strcat(parseList[forwardMostMove - 1], "#");
9460         break;
9461     }
9462     if (appData.debugMode) {
9463         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9464     }
9465
9466 }
9467
9468 /* Updates currentMove if not pausing */
9469 void
9470 ShowMove(fromX, fromY, toX, toY)
9471 {
9472     int instant = (gameMode == PlayFromGameFile) ?
9473         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9474     if(appData.noGUI) return;
9475     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9476         if (!instant) {
9477             if (forwardMostMove == currentMove + 1) {
9478                 AnimateMove(boards[forwardMostMove - 1],
9479                             fromX, fromY, toX, toY);
9480             }
9481             if (appData.highlightLastMove) {
9482                 SetHighlights(fromX, fromY, toX, toY);
9483             }
9484         }
9485         currentMove = forwardMostMove;
9486     }
9487
9488     if (instant) return;
9489
9490     DisplayMove(currentMove - 1);
9491     DrawPosition(FALSE, boards[currentMove]);
9492     DisplayBothClocks();
9493     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9494 }
9495
9496 void SendEgtPath(ChessProgramState *cps)
9497 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9498         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9499
9500         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9501
9502         while(*p) {
9503             char c, *q = name+1, *r, *s;
9504
9505             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9506             while(*p && *p != ',') *q++ = *p++;
9507             *q++ = ':'; *q = 0;
9508             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9509                 strcmp(name, ",nalimov:") == 0 ) {
9510                 // take nalimov path from the menu-changeable option first, if it is defined
9511               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9512                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9513             } else
9514             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9515                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9516                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9517                 s = r = StrStr(s, ":") + 1; // beginning of path info
9518                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9519                 c = *r; *r = 0;             // temporarily null-terminate path info
9520                     *--q = 0;               // strip of trailig ':' from name
9521                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9522                 *r = c;
9523                 SendToProgram(buf,cps);     // send egtbpath command for this format
9524             }
9525             if(*p == ',') p++; // read away comma to position for next format name
9526         }
9527 }
9528
9529 void
9530 InitChessProgram(cps, setup)
9531      ChessProgramState *cps;
9532      int setup; /* [HGM] needed to setup FRC opening position */
9533 {
9534     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9535     if (appData.noChessProgram) return;
9536     hintRequested = FALSE;
9537     bookRequested = FALSE;
9538
9539     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9540     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9541     if(cps->memSize) { /* [HGM] memory */
9542       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9543         SendToProgram(buf, cps);
9544     }
9545     SendEgtPath(cps); /* [HGM] EGT */
9546     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9547       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9548         SendToProgram(buf, cps);
9549     }
9550
9551     SendToProgram(cps->initString, cps);
9552     if (gameInfo.variant != VariantNormal &&
9553         gameInfo.variant != VariantLoadable
9554         /* [HGM] also send variant if board size non-standard */
9555         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9556                                             ) {
9557       char *v = VariantName(gameInfo.variant);
9558       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9559         /* [HGM] in protocol 1 we have to assume all variants valid */
9560         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9561         DisplayFatalError(buf, 0, 1);
9562         return;
9563       }
9564
9565       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9566       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9567       if( gameInfo.variant == VariantXiangqi )
9568            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9569       if( gameInfo.variant == VariantShogi )
9570            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9571       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9572            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9573       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9574           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9575            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9576       if( gameInfo.variant == VariantCourier )
9577            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9578       if( gameInfo.variant == VariantSuper )
9579            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9580       if( gameInfo.variant == VariantGreat )
9581            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9582       if( gameInfo.variant == VariantSChess )
9583            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9584       if( gameInfo.variant == VariantGrand )
9585            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9586
9587       if(overruled) {
9588         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9589                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9590            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9591            if(StrStr(cps->variants, b) == NULL) {
9592                // specific sized variant not known, check if general sizing allowed
9593                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9594                    if(StrStr(cps->variants, "boardsize") == NULL) {
9595                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9596                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9597                        DisplayFatalError(buf, 0, 1);
9598                        return;
9599                    }
9600                    /* [HGM] here we really should compare with the maximum supported board size */
9601                }
9602            }
9603       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9604       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9605       SendToProgram(buf, cps);
9606     }
9607     currentlyInitializedVariant = gameInfo.variant;
9608
9609     /* [HGM] send opening position in FRC to first engine */
9610     if(setup) {
9611           SendToProgram("force\n", cps);
9612           SendBoard(cps, 0);
9613           /* engine is now in force mode! Set flag to wake it up after first move. */
9614           setboardSpoiledMachineBlack = 1;
9615     }
9616
9617     if (cps->sendICS) {
9618       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9619       SendToProgram(buf, cps);
9620     }
9621     cps->maybeThinking = FALSE;
9622     cps->offeredDraw = 0;
9623     if (!appData.icsActive) {
9624         SendTimeControl(cps, movesPerSession, timeControl,
9625                         timeIncrement, appData.searchDepth,
9626                         searchTime);
9627     }
9628     if (appData.showThinking
9629         // [HGM] thinking: four options require thinking output to be sent
9630         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9631                                 ) {
9632         SendToProgram("post\n", cps);
9633     }
9634     SendToProgram("hard\n", cps);
9635     if (!appData.ponderNextMove) {
9636         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9637            it without being sure what state we are in first.  "hard"
9638            is not a toggle, so that one is OK.
9639          */
9640         SendToProgram("easy\n", cps);
9641     }
9642     if (cps->usePing) {
9643       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9644       SendToProgram(buf, cps);
9645     }
9646     cps->initDone = TRUE;
9647     ClearEngineOutputPane(cps == &second);
9648 }
9649
9650
9651 void
9652 StartChessProgram(cps)
9653      ChessProgramState *cps;
9654 {
9655     char buf[MSG_SIZ];
9656     int err;
9657
9658     if (appData.noChessProgram) return;
9659     cps->initDone = FALSE;
9660
9661     if (strcmp(cps->host, "localhost") == 0) {
9662         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9663     } else if (*appData.remoteShell == NULLCHAR) {
9664         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9665     } else {
9666         if (*appData.remoteUser == NULLCHAR) {
9667           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9668                     cps->program);
9669         } else {
9670           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9671                     cps->host, appData.remoteUser, cps->program);
9672         }
9673         err = StartChildProcess(buf, "", &cps->pr);
9674     }
9675
9676     if (err != 0) {
9677       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9678         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9679         if(cps != &first) return;
9680         appData.noChessProgram = TRUE;
9681         ThawUI();
9682         SetNCPMode();
9683 //      DisplayFatalError(buf, err, 1);
9684 //      cps->pr = NoProc;
9685 //      cps->isr = NULL;
9686         return;
9687     }
9688
9689     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9690     if (cps->protocolVersion > 1) {
9691       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9692       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9693       cps->comboCnt = 0;  //                and values of combo boxes
9694       SendToProgram(buf, cps);
9695     } else {
9696       SendToProgram("xboard\n", cps);
9697     }
9698 }
9699
9700 void
9701 TwoMachinesEventIfReady P((void))
9702 {
9703   static int curMess = 0;
9704   if (first.lastPing != first.lastPong) {
9705     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9706     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9707     return;
9708   }
9709   if (second.lastPing != second.lastPong) {
9710     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9711     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9712     return;
9713   }
9714   DisplayMessage("", ""); curMess = 0;
9715   ThawUI();
9716   TwoMachinesEvent();
9717 }
9718
9719 char *
9720 MakeName(char *template)
9721 {
9722     time_t clock;
9723     struct tm *tm;
9724     static char buf[MSG_SIZ];
9725     char *p = buf;
9726     int i;
9727
9728     clock = time((time_t *)NULL);
9729     tm = localtime(&clock);
9730
9731     while(*p++ = *template++) if(p[-1] == '%') {
9732         switch(*template++) {
9733           case 0:   *p = 0; return buf;
9734           case 'Y': i = tm->tm_year+1900; break;
9735           case 'y': i = tm->tm_year-100; break;
9736           case 'M': i = tm->tm_mon+1; break;
9737           case 'd': i = tm->tm_mday; break;
9738           case 'h': i = tm->tm_hour; break;
9739           case 'm': i = tm->tm_min; break;
9740           case 's': i = tm->tm_sec; break;
9741           default:  i = 0;
9742         }
9743         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9744     }
9745     return buf;
9746 }
9747
9748 int
9749 CountPlayers(char *p)
9750 {
9751     int n = 0;
9752     while(p = strchr(p, '\n')) p++, n++; // count participants
9753     return n;
9754 }
9755
9756 FILE *
9757 WriteTourneyFile(char *results, FILE *f)
9758 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9759     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9760     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9761         // create a file with tournament description
9762         fprintf(f, "-participants {%s}\n", appData.participants);
9763         fprintf(f, "-seedBase %d\n", appData.seedBase);
9764         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9765         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9766         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9767         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9768         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9769         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9770         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9771         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9772         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9773         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9774         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9775         if(searchTime > 0)
9776                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9777         else {
9778                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9779                 fprintf(f, "-tc %s\n", appData.timeControl);
9780                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9781         }
9782         fprintf(f, "-results \"%s\"\n", results);
9783     }
9784     return f;
9785 }
9786
9787 #define MAXENGINES 1000
9788 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9789
9790 void Substitute(char *participants, int expunge)
9791 {
9792     int i, changed, changes=0, nPlayers=0;
9793     char *p, *q, *r, buf[MSG_SIZ];
9794     if(participants == NULL) return;
9795     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9796     r = p = participants; q = appData.participants;
9797     while(*p && *p == *q) {
9798         if(*p == '\n') r = p+1, nPlayers++;
9799         p++; q++;
9800     }
9801     if(*p) { // difference
9802         while(*p && *p++ != '\n');
9803         while(*q && *q++ != '\n');
9804       changed = nPlayers;
9805         changes = 1 + (strcmp(p, q) != 0);
9806     }
9807     if(changes == 1) { // a single engine mnemonic was changed
9808         q = r; while(*q) nPlayers += (*q++ == '\n');
9809         p = buf; while(*r && (*p = *r++) != '\n') p++;
9810         *p = NULLCHAR;
9811         NamesToList(firstChessProgramNames, command, mnemonic);
9812         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9813         if(mnemonic[i]) { // The substitute is valid
9814             FILE *f;
9815             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9816                 flock(fileno(f), LOCK_EX);
9817                 ParseArgsFromFile(f);
9818                 fseek(f, 0, SEEK_SET);
9819                 FREE(appData.participants); appData.participants = participants;
9820                 if(expunge) { // erase results of replaced engine
9821                     int len = strlen(appData.results), w, b, dummy;
9822                     for(i=0; i<len; i++) {
9823                         Pairing(i, nPlayers, &w, &b, &dummy);
9824                         if((w == changed || b == changed) && appData.results[i] == '*') {
9825                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9826                             fclose(f);
9827                             return;
9828                         }
9829                     }
9830                     for(i=0; i<len; i++) {
9831                         Pairing(i, nPlayers, &w, &b, &dummy);
9832                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9833                     }
9834                 }
9835                 WriteTourneyFile(appData.results, f);
9836                 fclose(f); // release lock
9837                 return;
9838             }
9839         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9840     }
9841     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9842     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9843     free(participants);
9844     return;
9845 }
9846
9847 int
9848 CreateTourney(char *name)
9849 {
9850         FILE *f;
9851         if(matchMode && strcmp(name, appData.tourneyFile)) {
9852              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9853         }
9854         if(name[0] == NULLCHAR) {
9855             if(appData.participants[0])
9856                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9857             return 0;
9858         }
9859         f = fopen(name, "r");
9860         if(f) { // file exists
9861             ASSIGN(appData.tourneyFile, name);
9862             ParseArgsFromFile(f); // parse it
9863         } else {
9864             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9865             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9866                 DisplayError(_("Not enough participants"), 0);
9867                 return 0;
9868             }
9869             ASSIGN(appData.tourneyFile, name);
9870             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9871             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9872         }
9873         fclose(f);
9874         appData.noChessProgram = FALSE;
9875         appData.clockMode = TRUE;
9876         SetGNUMode();
9877         return 1;
9878 }
9879
9880 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9881 {
9882     char buf[MSG_SIZ], *p, *q;
9883     int i=1;
9884     while(*names) {
9885         p = names; q = buf;
9886         while(*p && *p != '\n') *q++ = *p++;
9887         *q = 0;
9888         if(engineList[i]) free(engineList[i]);
9889         engineList[i] = strdup(buf);
9890         if(*p == '\n') p++;
9891         TidyProgramName(engineList[i], "localhost", buf);
9892         if(engineMnemonic[i]) free(engineMnemonic[i]);
9893         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9894             strcat(buf, " (");
9895             sscanf(q + 8, "%s", buf + strlen(buf));
9896             strcat(buf, ")");
9897         }
9898         engineMnemonic[i] = strdup(buf);
9899         names = p; i++;
9900       if(i > MAXENGINES - 2) break;
9901     }
9902     engineList[i] = engineMnemonic[i] = NULL;
9903 }
9904
9905 // following implemented as macro to avoid type limitations
9906 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9907
9908 void SwapEngines(int n)
9909 {   // swap settings for first engine and other engine (so far only some selected options)
9910     int h;
9911     char *p;
9912     if(n == 0) return;
9913     SWAP(directory, p)
9914     SWAP(chessProgram, p)
9915     SWAP(isUCI, h)
9916     SWAP(hasOwnBookUCI, h)
9917     SWAP(protocolVersion, h)
9918     SWAP(reuse, h)
9919     SWAP(scoreIsAbsolute, h)
9920     SWAP(timeOdds, h)
9921     SWAP(logo, p)
9922     SWAP(pgnName, p)
9923     SWAP(pvSAN, h)
9924 }
9925
9926 void
9927 SetPlayer(int player)
9928 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9929     int i;
9930     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9931     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9932     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9933     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9934     if(mnemonic[i]) {
9935         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9936         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9937         ParseArgsFromString(buf);
9938     }
9939     free(engineName);
9940 }
9941
9942 int
9943 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9944 {   // determine players from game number
9945     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9946
9947     if(appData.tourneyType == 0) {
9948         roundsPerCycle = (nPlayers - 1) | 1;
9949         pairingsPerRound = nPlayers / 2;
9950     } else if(appData.tourneyType > 0) {
9951         roundsPerCycle = nPlayers - appData.tourneyType;
9952         pairingsPerRound = appData.tourneyType;
9953     }
9954     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9955     gamesPerCycle = gamesPerRound * roundsPerCycle;
9956     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9957     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9958     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9959     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9960     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9961     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9962
9963     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9964     if(appData.roundSync) *syncInterval = gamesPerRound;
9965
9966     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9967
9968     if(appData.tourneyType == 0) {
9969         if(curPairing == (nPlayers-1)/2 ) {
9970             *whitePlayer = curRound;
9971             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9972         } else {
9973             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9974             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9975             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9976             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9977         }
9978     } else if(appData.tourneyType > 0) {
9979         *whitePlayer = curPairing;
9980         *blackPlayer = curRound + appData.tourneyType;
9981     }
9982
9983     // take care of white/black alternation per round. 
9984     // For cycles and games this is already taken care of by default, derived from matchGame!
9985     return curRound & 1;
9986 }
9987
9988 int
9989 NextTourneyGame(int nr, int *swapColors)
9990 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9991     char *p, *q;
9992     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9993     FILE *tf;
9994     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9995     tf = fopen(appData.tourneyFile, "r");
9996     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9997     ParseArgsFromFile(tf); fclose(tf);
9998     InitTimeControls(); // TC might be altered from tourney file
9999
10000     nPlayers = CountPlayers(appData.participants); // count participants
10001     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10002     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10003
10004     if(syncInterval) {
10005         p = q = appData.results;
10006         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10007         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10008             DisplayMessage(_("Waiting for other game(s)"),"");
10009             waitingForGame = TRUE;
10010             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10011             return 0;
10012         }
10013         waitingForGame = FALSE;
10014     }
10015
10016     if(appData.tourneyType < 0) {
10017         if(nr>=0 && !pairingReceived) {
10018             char buf[1<<16];
10019             if(pairing.pr == NoProc) {
10020                 if(!appData.pairingEngine[0]) {
10021                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10022                     return 0;
10023                 }
10024                 StartChessProgram(&pairing); // starts the pairing engine
10025             }
10026             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10027             SendToProgram(buf, &pairing);
10028             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10029             SendToProgram(buf, &pairing);
10030             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10031         }
10032         pairingReceived = 0;                              // ... so we continue here 
10033         *swapColors = 0;
10034         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10035         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10036         matchGame = 1; roundNr = nr / syncInterval + 1;
10037     }
10038
10039     if(first.pr != NoProc) return 1; // engines already loaded
10040
10041     // redefine engines, engine dir, etc.
10042     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10043     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10044     SwapEngines(1);
10045     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10046     SwapEngines(1);         // and make that valid for second engine by swapping
10047     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10048     InitEngine(&second, 1);
10049     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10050     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10051     return 1;
10052 }
10053
10054 void
10055 NextMatchGame()
10056 {   // performs game initialization that does not invoke engines, and then tries to start the game
10057     int res, firstWhite, swapColors = 0;
10058     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10059     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10060     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10061     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10062     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10063     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10064     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10065     Reset(FALSE, first.pr != NoProc);
10066     res = LoadGameOrPosition(matchGame); // setup game
10067     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10068     if(!res) return; // abort when bad game/pos file
10069     TwoMachinesEvent();
10070 }
10071
10072 void UserAdjudicationEvent( int result )
10073 {
10074     ChessMove gameResult = GameIsDrawn;
10075
10076     if( result > 0 ) {
10077         gameResult = WhiteWins;
10078     }
10079     else if( result < 0 ) {
10080         gameResult = BlackWins;
10081     }
10082
10083     if( gameMode == TwoMachinesPlay ) {
10084         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10085     }
10086 }
10087
10088
10089 // [HGM] save: calculate checksum of game to make games easily identifiable
10090 int StringCheckSum(char *s)
10091 {
10092         int i = 0;
10093         if(s==NULL) return 0;
10094         while(*s) i = i*259 + *s++;
10095         return i;
10096 }
10097
10098 int GameCheckSum()
10099 {
10100         int i, sum=0;
10101         for(i=backwardMostMove; i<forwardMostMove; i++) {
10102                 sum += pvInfoList[i].depth;
10103                 sum += StringCheckSum(parseList[i]);
10104                 sum += StringCheckSum(commentList[i]);
10105                 sum *= 261;
10106         }
10107         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10108         return sum + StringCheckSum(commentList[i]);
10109 } // end of save patch
10110
10111 void
10112 GameEnds(result, resultDetails, whosays)
10113      ChessMove result;
10114      char *resultDetails;
10115      int whosays;
10116 {
10117     GameMode nextGameMode;
10118     int isIcsGame;
10119     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10120
10121     if(endingGame) return; /* [HGM] crash: forbid recursion */
10122     endingGame = 1;
10123     if(twoBoards) { // [HGM] dual: switch back to one board
10124         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10125         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10126     }
10127     if (appData.debugMode) {
10128       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10129               result, resultDetails ? resultDetails : "(null)", whosays);
10130     }
10131
10132     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10133
10134     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10135         /* If we are playing on ICS, the server decides when the
10136            game is over, but the engine can offer to draw, claim
10137            a draw, or resign.
10138          */
10139 #if ZIPPY
10140         if (appData.zippyPlay && first.initDone) {
10141             if (result == GameIsDrawn) {
10142                 /* In case draw still needs to be claimed */
10143                 SendToICS(ics_prefix);
10144                 SendToICS("draw\n");
10145             } else if (StrCaseStr(resultDetails, "resign")) {
10146                 SendToICS(ics_prefix);
10147                 SendToICS("resign\n");
10148             }
10149         }
10150 #endif
10151         endingGame = 0; /* [HGM] crash */
10152         return;
10153     }
10154
10155     /* If we're loading the game from a file, stop */
10156     if (whosays == GE_FILE) {
10157       (void) StopLoadGameTimer();
10158       gameFileFP = NULL;
10159     }
10160
10161     /* Cancel draw offers */
10162     first.offeredDraw = second.offeredDraw = 0;
10163
10164     /* If this is an ICS game, only ICS can really say it's done;
10165        if not, anyone can. */
10166     isIcsGame = (gameMode == IcsPlayingWhite ||
10167                  gameMode == IcsPlayingBlack ||
10168                  gameMode == IcsObserving    ||
10169                  gameMode == IcsExamining);
10170
10171     if (!isIcsGame || whosays == GE_ICS) {
10172         /* OK -- not an ICS game, or ICS said it was done */
10173         StopClocks();
10174         if (!isIcsGame && !appData.noChessProgram)
10175           SetUserThinkingEnables();
10176
10177         /* [HGM] if a machine claims the game end we verify this claim */
10178         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10179             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10180                 char claimer;
10181                 ChessMove trueResult = (ChessMove) -1;
10182
10183                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10184                                             first.twoMachinesColor[0] :
10185                                             second.twoMachinesColor[0] ;
10186
10187                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10188                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10189                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10190                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10191                 } else
10192                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10193                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10194                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10195                 } else
10196                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10197                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10198                 }
10199
10200                 // now verify win claims, but not in drop games, as we don't understand those yet
10201                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10202                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10203                     (result == WhiteWins && claimer == 'w' ||
10204                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10205                       if (appData.debugMode) {
10206                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10207                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10208                       }
10209                       if(result != trueResult) {
10210                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10211                               result = claimer == 'w' ? BlackWins : WhiteWins;
10212                               resultDetails = buf;
10213                       }
10214                 } else
10215                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10216                     && (forwardMostMove <= backwardMostMove ||
10217                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10218                         (claimer=='b')==(forwardMostMove&1))
10219                                                                                   ) {
10220                       /* [HGM] verify: draws that were not flagged are false claims */
10221                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10222                       result = claimer == 'w' ? BlackWins : WhiteWins;
10223                       resultDetails = buf;
10224                 }
10225                 /* (Claiming a loss is accepted no questions asked!) */
10226             }
10227             /* [HGM] bare: don't allow bare King to win */
10228             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10229                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10230                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10231                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10232                && result != GameIsDrawn)
10233             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10234                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10235                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10236                         if(p >= 0 && p <= (int)WhiteKing) k++;
10237                 }
10238                 if (appData.debugMode) {
10239                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10240                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10241                 }
10242                 if(k <= 1) {
10243                         result = GameIsDrawn;
10244                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10245                         resultDetails = buf;
10246                 }
10247             }
10248         }
10249
10250
10251         if(serverMoves != NULL && !loadFlag) { char c = '=';
10252             if(result==WhiteWins) c = '+';
10253             if(result==BlackWins) c = '-';
10254             if(resultDetails != NULL)
10255                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10256         }
10257         if (resultDetails != NULL) {
10258             gameInfo.result = result;
10259             gameInfo.resultDetails = StrSave(resultDetails);
10260
10261             /* display last move only if game was not loaded from file */
10262             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10263                 DisplayMove(currentMove - 1);
10264
10265             if (forwardMostMove != 0) {
10266                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10267                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10268                                                                 ) {
10269                     if (*appData.saveGameFile != NULLCHAR) {
10270                         SaveGameToFile(appData.saveGameFile, TRUE);
10271                     } else if (appData.autoSaveGames) {
10272                         AutoSaveGame();
10273                     }
10274                     if (*appData.savePositionFile != NULLCHAR) {
10275                         SavePositionToFile(appData.savePositionFile);
10276                     }
10277                 }
10278             }
10279
10280             /* Tell program how game ended in case it is learning */
10281             /* [HGM] Moved this to after saving the PGN, just in case */
10282             /* engine died and we got here through time loss. In that */
10283             /* case we will get a fatal error writing the pipe, which */
10284             /* would otherwise lose us the PGN.                       */
10285             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10286             /* output during GameEnds should never be fatal anymore   */
10287             if (gameMode == MachinePlaysWhite ||
10288                 gameMode == MachinePlaysBlack ||
10289                 gameMode == TwoMachinesPlay ||
10290                 gameMode == IcsPlayingWhite ||
10291                 gameMode == IcsPlayingBlack ||
10292                 gameMode == BeginningOfGame) {
10293                 char buf[MSG_SIZ];
10294                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10295                         resultDetails);
10296                 if (first.pr != NoProc) {
10297                     SendToProgram(buf, &first);
10298                 }
10299                 if (second.pr != NoProc &&
10300                     gameMode == TwoMachinesPlay) {
10301                     SendToProgram(buf, &second);
10302                 }
10303             }
10304         }
10305
10306         if (appData.icsActive) {
10307             if (appData.quietPlay &&
10308                 (gameMode == IcsPlayingWhite ||
10309                  gameMode == IcsPlayingBlack)) {
10310                 SendToICS(ics_prefix);
10311                 SendToICS("set shout 1\n");
10312             }
10313             nextGameMode = IcsIdle;
10314             ics_user_moved = FALSE;
10315             /* clean up premove.  It's ugly when the game has ended and the
10316              * premove highlights are still on the board.
10317              */
10318             if (gotPremove) {
10319               gotPremove = FALSE;
10320               ClearPremoveHighlights();
10321               DrawPosition(FALSE, boards[currentMove]);
10322             }
10323             if (whosays == GE_ICS) {
10324                 switch (result) {
10325                 case WhiteWins:
10326                     if (gameMode == IcsPlayingWhite)
10327                         PlayIcsWinSound();
10328                     else if(gameMode == IcsPlayingBlack)
10329                         PlayIcsLossSound();
10330                     break;
10331                 case BlackWins:
10332                     if (gameMode == IcsPlayingBlack)
10333                         PlayIcsWinSound();
10334                     else if(gameMode == IcsPlayingWhite)
10335                         PlayIcsLossSound();
10336                     break;
10337                 case GameIsDrawn:
10338                     PlayIcsDrawSound();
10339                     break;
10340                 default:
10341                     PlayIcsUnfinishedSound();
10342                 }
10343             }
10344         } else if (gameMode == EditGame ||
10345                    gameMode == PlayFromGameFile ||
10346                    gameMode == AnalyzeMode ||
10347                    gameMode == AnalyzeFile) {
10348             nextGameMode = gameMode;
10349         } else {
10350             nextGameMode = EndOfGame;
10351         }
10352         pausing = FALSE;
10353         ModeHighlight();
10354     } else {
10355         nextGameMode = gameMode;
10356     }
10357
10358     if (appData.noChessProgram) {
10359         gameMode = nextGameMode;
10360         ModeHighlight();
10361         endingGame = 0; /* [HGM] crash */
10362         return;
10363     }
10364
10365     if (first.reuse) {
10366         /* Put first chess program into idle state */
10367         if (first.pr != NoProc &&
10368             (gameMode == MachinePlaysWhite ||
10369              gameMode == MachinePlaysBlack ||
10370              gameMode == TwoMachinesPlay ||
10371              gameMode == IcsPlayingWhite ||
10372              gameMode == IcsPlayingBlack ||
10373              gameMode == BeginningOfGame)) {
10374             SendToProgram("force\n", &first);
10375             if (first.usePing) {
10376               char buf[MSG_SIZ];
10377               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10378               SendToProgram(buf, &first);
10379             }
10380         }
10381     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10382         /* Kill off first chess program */
10383         if (first.isr != NULL)
10384           RemoveInputSource(first.isr);
10385         first.isr = NULL;
10386
10387         if (first.pr != NoProc) {
10388             ExitAnalyzeMode();
10389             DoSleep( appData.delayBeforeQuit );
10390             SendToProgram("quit\n", &first);
10391             DoSleep( appData.delayAfterQuit );
10392             DestroyChildProcess(first.pr, first.useSigterm);
10393         }
10394         first.pr = NoProc;
10395     }
10396     if (second.reuse) {
10397         /* Put second chess program into idle state */
10398         if (second.pr != NoProc &&
10399             gameMode == TwoMachinesPlay) {
10400             SendToProgram("force\n", &second);
10401             if (second.usePing) {
10402               char buf[MSG_SIZ];
10403               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10404               SendToProgram(buf, &second);
10405             }
10406         }
10407     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10408         /* Kill off second chess program */
10409         if (second.isr != NULL)
10410           RemoveInputSource(second.isr);
10411         second.isr = NULL;
10412
10413         if (second.pr != NoProc) {
10414             DoSleep( appData.delayBeforeQuit );
10415             SendToProgram("quit\n", &second);
10416             DoSleep( appData.delayAfterQuit );
10417             DestroyChildProcess(second.pr, second.useSigterm);
10418         }
10419         second.pr = NoProc;
10420     }
10421
10422     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10423         char resChar = '=';
10424         switch (result) {
10425         case WhiteWins:
10426           resChar = '+';
10427           if (first.twoMachinesColor[0] == 'w') {
10428             first.matchWins++;
10429           } else {
10430             second.matchWins++;
10431           }
10432           break;
10433         case BlackWins:
10434           resChar = '-';
10435           if (first.twoMachinesColor[0] == 'b') {
10436             first.matchWins++;
10437           } else {
10438             second.matchWins++;
10439           }
10440           break;
10441         case GameUnfinished:
10442           resChar = ' ';
10443         default:
10444           break;
10445         }
10446
10447         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10448         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10449             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10450             ReserveGame(nextGame, resChar); // sets nextGame
10451             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10452             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10453         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10454
10455         if (nextGame <= appData.matchGames && !abortMatch) {
10456             gameMode = nextGameMode;
10457             matchGame = nextGame; // this will be overruled in tourney mode!
10458             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10459             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10460             endingGame = 0; /* [HGM] crash */
10461             return;
10462         } else {
10463             gameMode = nextGameMode;
10464             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10465                      first.tidy, second.tidy,
10466                      first.matchWins, second.matchWins,
10467                      appData.matchGames - (first.matchWins + second.matchWins));
10468             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10469             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10470             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10471                 first.twoMachinesColor = "black\n";
10472                 second.twoMachinesColor = "white\n";
10473             } else {
10474                 first.twoMachinesColor = "white\n";
10475                 second.twoMachinesColor = "black\n";
10476             }
10477         }
10478     }
10479     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10480         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10481       ExitAnalyzeMode();
10482     gameMode = nextGameMode;
10483     ModeHighlight();
10484     endingGame = 0;  /* [HGM] crash */
10485     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10486         if(matchMode == TRUE) { // match through command line: exit with or without popup
10487             if(ranking) {
10488                 ToNrEvent(forwardMostMove);
10489                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10490                 else ExitEvent(0);
10491             } else DisplayFatalError(buf, 0, 0);
10492         } else { // match through menu; just stop, with or without popup
10493             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10494             ModeHighlight();
10495             if(ranking){
10496                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10497             } else DisplayNote(buf);
10498       }
10499       if(ranking) free(ranking);
10500     }
10501 }
10502
10503 /* Assumes program was just initialized (initString sent).
10504    Leaves program in force mode. */
10505 void
10506 FeedMovesToProgram(cps, upto)
10507      ChessProgramState *cps;
10508      int upto;
10509 {
10510     int i;
10511
10512     if (appData.debugMode)
10513       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10514               startedFromSetupPosition ? "position and " : "",
10515               backwardMostMove, upto, cps->which);
10516     if(currentlyInitializedVariant != gameInfo.variant) {
10517       char buf[MSG_SIZ];
10518         // [HGM] variantswitch: make engine aware of new variant
10519         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10520                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10521         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10522         SendToProgram(buf, cps);
10523         currentlyInitializedVariant = gameInfo.variant;
10524     }
10525     SendToProgram("force\n", cps);
10526     if (startedFromSetupPosition) {
10527         SendBoard(cps, backwardMostMove);
10528     if (appData.debugMode) {
10529         fprintf(debugFP, "feedMoves\n");
10530     }
10531     }
10532     for (i = backwardMostMove; i < upto; i++) {
10533         SendMoveToProgram(i, cps);
10534     }
10535 }
10536
10537
10538 int
10539 ResurrectChessProgram()
10540 {
10541      /* The chess program may have exited.
10542         If so, restart it and feed it all the moves made so far. */
10543     static int doInit = 0;
10544
10545     if (appData.noChessProgram) return 1;
10546
10547     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10548         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10549         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10550         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10551     } else {
10552         if (first.pr != NoProc) return 1;
10553         StartChessProgram(&first);
10554     }
10555     InitChessProgram(&first, FALSE);
10556     FeedMovesToProgram(&first, currentMove);
10557
10558     if (!first.sendTime) {
10559         /* can't tell gnuchess what its clock should read,
10560            so we bow to its notion. */
10561         ResetClocks();
10562         timeRemaining[0][currentMove] = whiteTimeRemaining;
10563         timeRemaining[1][currentMove] = blackTimeRemaining;
10564     }
10565
10566     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10567                 appData.icsEngineAnalyze) && first.analysisSupport) {
10568       SendToProgram("analyze\n", &first);
10569       first.analyzing = TRUE;
10570     }
10571     return 1;
10572 }
10573
10574 /*
10575  * Button procedures
10576  */
10577 void
10578 Reset(redraw, init)
10579      int redraw, init;
10580 {
10581     int i;
10582
10583     if (appData.debugMode) {
10584         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10585                 redraw, init, gameMode);
10586     }
10587     CleanupTail(); // [HGM] vari: delete any stored variations
10588     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10589     pausing = pauseExamInvalid = FALSE;
10590     startedFromSetupPosition = blackPlaysFirst = FALSE;
10591     firstMove = TRUE;
10592     whiteFlag = blackFlag = FALSE;
10593     userOfferedDraw = FALSE;
10594     hintRequested = bookRequested = FALSE;
10595     first.maybeThinking = FALSE;
10596     second.maybeThinking = FALSE;
10597     first.bookSuspend = FALSE; // [HGM] book
10598     second.bookSuspend = FALSE;
10599     thinkOutput[0] = NULLCHAR;
10600     lastHint[0] = NULLCHAR;
10601     ClearGameInfo(&gameInfo);
10602     gameInfo.variant = StringToVariant(appData.variant);
10603     ics_user_moved = ics_clock_paused = FALSE;
10604     ics_getting_history = H_FALSE;
10605     ics_gamenum = -1;
10606     white_holding[0] = black_holding[0] = NULLCHAR;
10607     ClearProgramStats();
10608     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10609
10610     ResetFrontEnd();
10611     ClearHighlights();
10612     flipView = appData.flipView;
10613     ClearPremoveHighlights();
10614     gotPremove = FALSE;
10615     alarmSounded = FALSE;
10616
10617     GameEnds(EndOfFile, NULL, GE_PLAYER);
10618     if(appData.serverMovesName != NULL) {
10619         /* [HGM] prepare to make moves file for broadcasting */
10620         clock_t t = clock();
10621         if(serverMoves != NULL) fclose(serverMoves);
10622         serverMoves = fopen(appData.serverMovesName, "r");
10623         if(serverMoves != NULL) {
10624             fclose(serverMoves);
10625             /* delay 15 sec before overwriting, so all clients can see end */
10626             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10627         }
10628         serverMoves = fopen(appData.serverMovesName, "w");
10629     }
10630
10631     ExitAnalyzeMode();
10632     gameMode = BeginningOfGame;
10633     ModeHighlight();
10634     if(appData.icsActive) gameInfo.variant = VariantNormal;
10635     currentMove = forwardMostMove = backwardMostMove = 0;
10636     InitPosition(redraw);
10637     for (i = 0; i < MAX_MOVES; i++) {
10638         if (commentList[i] != NULL) {
10639             free(commentList[i]);
10640             commentList[i] = NULL;
10641         }
10642     }
10643     ResetClocks();
10644     timeRemaining[0][0] = whiteTimeRemaining;
10645     timeRemaining[1][0] = blackTimeRemaining;
10646
10647     if (first.pr == NULL) {
10648         StartChessProgram(&first);
10649     }
10650     if (init) {
10651             InitChessProgram(&first, startedFromSetupPosition);
10652     }
10653     DisplayTitle("");
10654     DisplayMessage("", "");
10655     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10656     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10657 }
10658
10659 void
10660 AutoPlayGameLoop()
10661 {
10662     for (;;) {
10663         if (!AutoPlayOneMove())
10664           return;
10665         if (matchMode || appData.timeDelay == 0)
10666           continue;
10667         if (appData.timeDelay < 0)
10668           return;
10669         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10670         break;
10671     }
10672 }
10673
10674
10675 int
10676 AutoPlayOneMove()
10677 {
10678     int fromX, fromY, toX, toY;
10679
10680     if (appData.debugMode) {
10681       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10682     }
10683
10684     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10685       return FALSE;
10686
10687     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10688       pvInfoList[currentMove].depth = programStats.depth;
10689       pvInfoList[currentMove].score = programStats.score;
10690       pvInfoList[currentMove].time  = 0;
10691       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10692     }
10693
10694     if (currentMove >= forwardMostMove) {
10695       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10696 //      gameMode = EndOfGame;
10697 //      ModeHighlight();
10698
10699       /* [AS] Clear current move marker at the end of a game */
10700       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10701
10702       return FALSE;
10703     }
10704
10705     toX = moveList[currentMove][2] - AAA;
10706     toY = moveList[currentMove][3] - ONE;
10707
10708     if (moveList[currentMove][1] == '@') {
10709         if (appData.highlightLastMove) {
10710             SetHighlights(-1, -1, toX, toY);
10711         }
10712     } else {
10713         fromX = moveList[currentMove][0] - AAA;
10714         fromY = moveList[currentMove][1] - ONE;
10715
10716         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10717
10718         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10719
10720         if (appData.highlightLastMove) {
10721             SetHighlights(fromX, fromY, toX, toY);
10722         }
10723     }
10724     DisplayMove(currentMove);
10725     SendMoveToProgram(currentMove++, &first);
10726     DisplayBothClocks();
10727     DrawPosition(FALSE, boards[currentMove]);
10728     // [HGM] PV info: always display, routine tests if empty
10729     DisplayComment(currentMove - 1, commentList[currentMove]);
10730     return TRUE;
10731 }
10732
10733
10734 int
10735 LoadGameOneMove(readAhead)
10736      ChessMove readAhead;
10737 {
10738     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10739     char promoChar = NULLCHAR;
10740     ChessMove moveType;
10741     char move[MSG_SIZ];
10742     char *p, *q;
10743
10744     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10745         gameMode != AnalyzeMode && gameMode != Training) {
10746         gameFileFP = NULL;
10747         return FALSE;
10748     }
10749
10750     yyboardindex = forwardMostMove;
10751     if (readAhead != EndOfFile) {
10752       moveType = readAhead;
10753     } else {
10754       if (gameFileFP == NULL)
10755           return FALSE;
10756       moveType = (ChessMove) Myylex();
10757     }
10758
10759     done = FALSE;
10760     switch (moveType) {
10761       case Comment:
10762         if (appData.debugMode)
10763           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10764         p = yy_text;
10765
10766         /* append the comment but don't display it */
10767         AppendComment(currentMove, p, FALSE);
10768         return TRUE;
10769
10770       case WhiteCapturesEnPassant:
10771       case BlackCapturesEnPassant:
10772       case WhitePromotion:
10773       case BlackPromotion:
10774       case WhiteNonPromotion:
10775       case BlackNonPromotion:
10776       case NormalMove:
10777       case WhiteKingSideCastle:
10778       case WhiteQueenSideCastle:
10779       case BlackKingSideCastle:
10780       case BlackQueenSideCastle:
10781       case WhiteKingSideCastleWild:
10782       case WhiteQueenSideCastleWild:
10783       case BlackKingSideCastleWild:
10784       case BlackQueenSideCastleWild:
10785       /* PUSH Fabien */
10786       case WhiteHSideCastleFR:
10787       case WhiteASideCastleFR:
10788       case BlackHSideCastleFR:
10789       case BlackASideCastleFR:
10790       /* POP Fabien */
10791         if (appData.debugMode)
10792           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10793         fromX = currentMoveString[0] - AAA;
10794         fromY = currentMoveString[1] - ONE;
10795         toX = currentMoveString[2] - AAA;
10796         toY = currentMoveString[3] - ONE;
10797         promoChar = currentMoveString[4];
10798         break;
10799
10800       case WhiteDrop:
10801       case BlackDrop:
10802         if (appData.debugMode)
10803           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10804         fromX = moveType == WhiteDrop ?
10805           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10806         (int) CharToPiece(ToLower(currentMoveString[0]));
10807         fromY = DROP_RANK;
10808         toX = currentMoveString[2] - AAA;
10809         toY = currentMoveString[3] - ONE;
10810         break;
10811
10812       case WhiteWins:
10813       case BlackWins:
10814       case GameIsDrawn:
10815       case GameUnfinished:
10816         if (appData.debugMode)
10817           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10818         p = strchr(yy_text, '{');
10819         if (p == NULL) p = strchr(yy_text, '(');
10820         if (p == NULL) {
10821             p = yy_text;
10822             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10823         } else {
10824             q = strchr(p, *p == '{' ? '}' : ')');
10825             if (q != NULL) *q = NULLCHAR;
10826             p++;
10827         }
10828         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10829         GameEnds(moveType, p, GE_FILE);
10830         done = TRUE;
10831         if (cmailMsgLoaded) {
10832             ClearHighlights();
10833             flipView = WhiteOnMove(currentMove);
10834             if (moveType == GameUnfinished) flipView = !flipView;
10835             if (appData.debugMode)
10836               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10837         }
10838         break;
10839
10840       case EndOfFile:
10841         if (appData.debugMode)
10842           fprintf(debugFP, "Parser hit end of file\n");
10843         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10844           case MT_NONE:
10845           case MT_CHECK:
10846             break;
10847           case MT_CHECKMATE:
10848           case MT_STAINMATE:
10849             if (WhiteOnMove(currentMove)) {
10850                 GameEnds(BlackWins, "Black mates", GE_FILE);
10851             } else {
10852                 GameEnds(WhiteWins, "White mates", GE_FILE);
10853             }
10854             break;
10855           case MT_STALEMATE:
10856             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10857             break;
10858         }
10859         done = TRUE;
10860         break;
10861
10862       case MoveNumberOne:
10863         if (lastLoadGameStart == GNUChessGame) {
10864             /* GNUChessGames have numbers, but they aren't move numbers */
10865             if (appData.debugMode)
10866               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10867                       yy_text, (int) moveType);
10868             return LoadGameOneMove(EndOfFile); /* tail recursion */
10869         }
10870         /* else fall thru */
10871
10872       case XBoardGame:
10873       case GNUChessGame:
10874       case PGNTag:
10875         /* Reached start of next game in file */
10876         if (appData.debugMode)
10877           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10878         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10879           case MT_NONE:
10880           case MT_CHECK:
10881             break;
10882           case MT_CHECKMATE:
10883           case MT_STAINMATE:
10884             if (WhiteOnMove(currentMove)) {
10885                 GameEnds(BlackWins, "Black mates", GE_FILE);
10886             } else {
10887                 GameEnds(WhiteWins, "White mates", GE_FILE);
10888             }
10889             break;
10890           case MT_STALEMATE:
10891             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10892             break;
10893         }
10894         done = TRUE;
10895         break;
10896
10897       case PositionDiagram:     /* should not happen; ignore */
10898       case ElapsedTime:         /* ignore */
10899       case NAG:                 /* ignore */
10900         if (appData.debugMode)
10901           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10902                   yy_text, (int) moveType);
10903         return LoadGameOneMove(EndOfFile); /* tail recursion */
10904
10905       case IllegalMove:
10906         if (appData.testLegality) {
10907             if (appData.debugMode)
10908               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10909             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10910                     (forwardMostMove / 2) + 1,
10911                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10912             DisplayError(move, 0);
10913             done = TRUE;
10914         } else {
10915             if (appData.debugMode)
10916               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10917                       yy_text, currentMoveString);
10918             fromX = currentMoveString[0] - AAA;
10919             fromY = currentMoveString[1] - ONE;
10920             toX = currentMoveString[2] - AAA;
10921             toY = currentMoveString[3] - ONE;
10922             promoChar = currentMoveString[4];
10923         }
10924         break;
10925
10926       case AmbiguousMove:
10927         if (appData.debugMode)
10928           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10929         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10930                 (forwardMostMove / 2) + 1,
10931                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10932         DisplayError(move, 0);
10933         done = TRUE;
10934         break;
10935
10936       default:
10937       case ImpossibleMove:
10938         if (appData.debugMode)
10939           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10940         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10941                 (forwardMostMove / 2) + 1,
10942                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10943         DisplayError(move, 0);
10944         done = TRUE;
10945         break;
10946     }
10947
10948     if (done) {
10949         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10950             DrawPosition(FALSE, boards[currentMove]);
10951             DisplayBothClocks();
10952             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10953               DisplayComment(currentMove - 1, commentList[currentMove]);
10954         }
10955         (void) StopLoadGameTimer();
10956         gameFileFP = NULL;
10957         cmailOldMove = forwardMostMove;
10958         return FALSE;
10959     } else {
10960         /* currentMoveString is set as a side-effect of yylex */
10961
10962         thinkOutput[0] = NULLCHAR;
10963         MakeMove(fromX, fromY, toX, toY, promoChar);
10964         currentMove = forwardMostMove;
10965         return TRUE;
10966     }
10967 }
10968
10969 /* Load the nth game from the given file */
10970 int
10971 LoadGameFromFile(filename, n, title, useList)
10972      char *filename;
10973      int n;
10974      char *title;
10975      /*Boolean*/ int useList;
10976 {
10977     FILE *f;
10978     char buf[MSG_SIZ];
10979
10980     if (strcmp(filename, "-") == 0) {
10981         f = stdin;
10982         title = "stdin";
10983     } else {
10984         f = fopen(filename, "rb");
10985         if (f == NULL) {
10986           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10987             DisplayError(buf, errno);
10988             return FALSE;
10989         }
10990     }
10991     if (fseek(f, 0, 0) == -1) {
10992         /* f is not seekable; probably a pipe */
10993         useList = FALSE;
10994     }
10995     if (useList && n == 0) {
10996         int error = GameListBuild(f);
10997         if (error) {
10998             DisplayError(_("Cannot build game list"), error);
10999         } else if (!ListEmpty(&gameList) &&
11000                    ((ListGame *) gameList.tailPred)->number > 1) {
11001             GameListPopUp(f, title);
11002             return TRUE;
11003         }
11004         GameListDestroy();
11005         n = 1;
11006     }
11007     if (n == 0) n = 1;
11008     return LoadGame(f, n, title, FALSE);
11009 }
11010
11011
11012 void
11013 MakeRegisteredMove()
11014 {
11015     int fromX, fromY, toX, toY;
11016     char promoChar;
11017     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11018         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11019           case CMAIL_MOVE:
11020           case CMAIL_DRAW:
11021             if (appData.debugMode)
11022               fprintf(debugFP, "Restoring %s for game %d\n",
11023                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11024
11025             thinkOutput[0] = NULLCHAR;
11026             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11027             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11028             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11029             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11030             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11031             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11032             MakeMove(fromX, fromY, toX, toY, promoChar);
11033             ShowMove(fromX, fromY, toX, toY);
11034
11035             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11036               case MT_NONE:
11037               case MT_CHECK:
11038                 break;
11039
11040               case MT_CHECKMATE:
11041               case MT_STAINMATE:
11042                 if (WhiteOnMove(currentMove)) {
11043                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11044                 } else {
11045                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11046                 }
11047                 break;
11048
11049               case MT_STALEMATE:
11050                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11051                 break;
11052             }
11053
11054             break;
11055
11056           case CMAIL_RESIGN:
11057             if (WhiteOnMove(currentMove)) {
11058                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11059             } else {
11060                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11061             }
11062             break;
11063
11064           case CMAIL_ACCEPT:
11065             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11066             break;
11067
11068           default:
11069             break;
11070         }
11071     }
11072
11073     return;
11074 }
11075
11076 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11077 int
11078 CmailLoadGame(f, gameNumber, title, useList)
11079      FILE *f;
11080      int gameNumber;
11081      char *title;
11082      int useList;
11083 {
11084     int retVal;
11085
11086     if (gameNumber > nCmailGames) {
11087         DisplayError(_("No more games in this message"), 0);
11088         return FALSE;
11089     }
11090     if (f == lastLoadGameFP) {
11091         int offset = gameNumber - lastLoadGameNumber;
11092         if (offset == 0) {
11093             cmailMsg[0] = NULLCHAR;
11094             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11095                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11096                 nCmailMovesRegistered--;
11097             }
11098             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11099             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11100                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11101             }
11102         } else {
11103             if (! RegisterMove()) return FALSE;
11104         }
11105     }
11106
11107     retVal = LoadGame(f, gameNumber, title, useList);
11108
11109     /* Make move registered during previous look at this game, if any */
11110     MakeRegisteredMove();
11111
11112     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11113         commentList[currentMove]
11114           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11115         DisplayComment(currentMove - 1, commentList[currentMove]);
11116     }
11117
11118     return retVal;
11119 }
11120
11121 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11122 int
11123 ReloadGame(offset)
11124      int offset;
11125 {
11126     int gameNumber = lastLoadGameNumber + offset;
11127     if (lastLoadGameFP == NULL) {
11128         DisplayError(_("No game has been loaded yet"), 0);
11129         return FALSE;
11130     }
11131     if (gameNumber <= 0) {
11132         DisplayError(_("Can't back up any further"), 0);
11133         return FALSE;
11134     }
11135     if (cmailMsgLoaded) {
11136         return CmailLoadGame(lastLoadGameFP, gameNumber,
11137                              lastLoadGameTitle, lastLoadGameUseList);
11138     } else {
11139         return LoadGame(lastLoadGameFP, gameNumber,
11140                         lastLoadGameTitle, lastLoadGameUseList);
11141     }
11142 }
11143
11144 int keys[EmptySquare+1];
11145
11146 int
11147 PositionMatches(Board b1, Board b2)
11148 {
11149     int r, f, sum=0;
11150     switch(appData.searchMode) {
11151         case 1: return CompareWithRights(b1, b2);
11152         case 2:
11153             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11154                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11155             }
11156             return TRUE;
11157         case 3:
11158             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11159               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11160                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11161             }
11162             return sum==0;
11163         case 4:
11164             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11165                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11166             }
11167             return sum==0;
11168     }
11169     return TRUE;
11170 }
11171
11172 GameInfo dummyInfo;
11173
11174 int GameContainsPosition(FILE *f, ListGame *lg)
11175 {
11176     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11177     int fromX, fromY, toX, toY;
11178     char promoChar;
11179     static int initDone=FALSE;
11180
11181     if(!initDone) {
11182         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11183         initDone = TRUE;
11184     }
11185     dummyInfo.variant = VariantNormal;
11186     FREE(dummyInfo.fen); dummyInfo.fen = NULL;
11187     dummyInfo.whiteRating = 0;
11188     dummyInfo.blackRating = 0;
11189     FREE(dummyInfo.date); dummyInfo.date = NULL;
11190     fseek(f, lg->offset, 0);
11191     yynewfile(f);
11192     CopyBoard(boards[scratch], initialPosition); // default start position
11193     while(1) {
11194         yyboardindex = scratch + (plyNr&1);
11195       quickFlag = 1;
11196         next = Myylex();
11197       quickFlag = 0;
11198         switch(next) {
11199             case PGNTag:
11200                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11201 #if 0
11202                 ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
11203                 if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
11204 #else
11205                 // do it ourselves avoiding malloc
11206                 { char *p = yy_text+1, *q;
11207                   while(!isdigit(*p) && !isalpha(*p)) p++;
11208                   q  = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
11209                   *p = NULLCHAR;
11210                   if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
11211                   if(!StrCaseCmp(q, "Variant")  &&  (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
11212                   if(!StrCaseCmp(q, "WhiteElo")  && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11213                   if(!StrCaseCmp(q, "BlackElo")  && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11214                   if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
11215                   if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
11216                   if(!StrCaseCmp(q, "FEN")  && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
11217                 }
11218 #endif
11219             default:
11220                 continue;
11221
11222             case XBoardGame:
11223             case GNUChessGame:
11224                 if(plyNr) return -1; // after we have seen moves, this is for new game
11225               continue;
11226
11227             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11228             case ImpossibleMove:
11229             case WhiteWins: // game ends here with these four
11230             case BlackWins:
11231             case GameIsDrawn:
11232             case GameUnfinished:
11233                 return -1;
11234
11235             case IllegalMove:
11236                 if(appData.testLegality) return -1;
11237             case WhiteCapturesEnPassant:
11238             case BlackCapturesEnPassant:
11239             case WhitePromotion:
11240             case BlackPromotion:
11241             case WhiteNonPromotion:
11242             case BlackNonPromotion:
11243             case NormalMove:
11244             case WhiteKingSideCastle:
11245             case WhiteQueenSideCastle:
11246             case BlackKingSideCastle:
11247             case BlackQueenSideCastle:
11248             case WhiteKingSideCastleWild:
11249             case WhiteQueenSideCastleWild:
11250             case BlackKingSideCastleWild:
11251             case BlackQueenSideCastleWild:
11252             case WhiteHSideCastleFR:
11253             case WhiteASideCastleFR:
11254             case BlackHSideCastleFR:
11255             case BlackASideCastleFR:
11256                 fromX = currentMoveString[0] - AAA;
11257                 fromY = currentMoveString[1] - ONE;
11258                 toX = currentMoveString[2] - AAA;
11259                 toY = currentMoveString[3] - ONE;
11260                 promoChar = currentMoveString[4];
11261                 break;
11262             case WhiteDrop:
11263             case BlackDrop:
11264                 fromX = next == WhiteDrop ?
11265                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11266                   (int) CharToPiece(ToLower(currentMoveString[0]));
11267                 fromY = DROP_RANK;
11268                 toX = currentMoveString[2] - AAA;
11269                 toY = currentMoveString[3] - ONE;
11270                 break;
11271         }
11272         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11273         if(plyNr == 0) { // but first figure out variant and initial position
11274             if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
11275             if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
11276             if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
11277             if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
11278             if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
11279             if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
11280         }
11281         CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
11282         plyNr++;
11283         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
11284         if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
11285     }
11286 }
11287
11288 /* Load the nth game from open file f */
11289 int
11290 LoadGame(f, gameNumber, title, useList)
11291      FILE *f;
11292      int gameNumber;
11293      char *title;
11294      int useList;
11295 {
11296     ChessMove cm;
11297     char buf[MSG_SIZ];
11298     int gn = gameNumber;
11299     ListGame *lg = NULL;
11300     int numPGNTags = 0;
11301     int err, pos = -1;
11302     GameMode oldGameMode;
11303     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11304
11305     if (appData.debugMode)
11306         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11307
11308     if (gameMode == Training )
11309         SetTrainingModeOff();
11310
11311     oldGameMode = gameMode;
11312     if (gameMode != BeginningOfGame) {
11313       Reset(FALSE, TRUE);
11314     }
11315
11316     gameFileFP = f;
11317     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11318         fclose(lastLoadGameFP);
11319     }
11320
11321     if (useList) {
11322         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11323
11324         if (lg) {
11325             fseek(f, lg->offset, 0);
11326             GameListHighlight(gameNumber);
11327             pos = lg->position;
11328             gn = 1;
11329         }
11330         else {
11331             DisplayError(_("Game number out of range"), 0);
11332             return FALSE;
11333         }
11334     } else {
11335         GameListDestroy();
11336         if (fseek(f, 0, 0) == -1) {
11337             if (f == lastLoadGameFP ?
11338                 gameNumber == lastLoadGameNumber + 1 :
11339                 gameNumber == 1) {
11340                 gn = 1;
11341             } else {
11342                 DisplayError(_("Can't seek on game file"), 0);
11343                 return FALSE;
11344             }
11345         }
11346     }
11347     lastLoadGameFP = f;
11348     lastLoadGameNumber = gameNumber;
11349     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11350     lastLoadGameUseList = useList;
11351
11352     yynewfile(f);
11353
11354     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11355       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11356                 lg->gameInfo.black);
11357             DisplayTitle(buf);
11358     } else if (*title != NULLCHAR) {
11359         if (gameNumber > 1) {
11360           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11361             DisplayTitle(buf);
11362         } else {
11363             DisplayTitle(title);
11364         }
11365     }
11366
11367     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11368         gameMode = PlayFromGameFile;
11369         ModeHighlight();
11370     }
11371
11372     currentMove = forwardMostMove = backwardMostMove = 0;
11373     CopyBoard(boards[0], initialPosition);
11374     StopClocks();
11375
11376     /*
11377      * Skip the first gn-1 games in the file.
11378      * Also skip over anything that precedes an identifiable
11379      * start of game marker, to avoid being confused by
11380      * garbage at the start of the file.  Currently
11381      * recognized start of game markers are the move number "1",
11382      * the pattern "gnuchess .* game", the pattern
11383      * "^[#;%] [^ ]* game file", and a PGN tag block.
11384      * A game that starts with one of the latter two patterns
11385      * will also have a move number 1, possibly
11386      * following a position diagram.
11387      * 5-4-02: Let's try being more lenient and allowing a game to
11388      * start with an unnumbered move.  Does that break anything?
11389      */
11390     cm = lastLoadGameStart = EndOfFile;
11391     while (gn > 0) {
11392         yyboardindex = forwardMostMove;
11393         cm = (ChessMove) Myylex();
11394         switch (cm) {
11395           case EndOfFile:
11396             if (cmailMsgLoaded) {
11397                 nCmailGames = CMAIL_MAX_GAMES - gn;
11398             } else {
11399                 Reset(TRUE, TRUE);
11400                 DisplayError(_("Game not found in file"), 0);
11401             }
11402             return FALSE;
11403
11404           case GNUChessGame:
11405           case XBoardGame:
11406             gn--;
11407             lastLoadGameStart = cm;
11408             break;
11409
11410           case MoveNumberOne:
11411             switch (lastLoadGameStart) {
11412               case GNUChessGame:
11413               case XBoardGame:
11414               case PGNTag:
11415                 break;
11416               case MoveNumberOne:
11417               case EndOfFile:
11418                 gn--;           /* count this game */
11419                 lastLoadGameStart = cm;
11420                 break;
11421               default:
11422                 /* impossible */
11423                 break;
11424             }
11425             break;
11426
11427           case PGNTag:
11428             switch (lastLoadGameStart) {
11429               case GNUChessGame:
11430               case PGNTag:
11431               case MoveNumberOne:
11432               case EndOfFile:
11433                 gn--;           /* count this game */
11434                 lastLoadGameStart = cm;
11435                 break;
11436               case XBoardGame:
11437                 lastLoadGameStart = cm; /* game counted already */
11438                 break;
11439               default:
11440                 /* impossible */
11441                 break;
11442             }
11443             if (gn > 0) {
11444                 do {
11445                     yyboardindex = forwardMostMove;
11446                     cm = (ChessMove) Myylex();
11447                 } while (cm == PGNTag || cm == Comment);
11448             }
11449             break;
11450
11451           case WhiteWins:
11452           case BlackWins:
11453           case GameIsDrawn:
11454             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11455                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11456                     != CMAIL_OLD_RESULT) {
11457                     nCmailResults ++ ;
11458                     cmailResult[  CMAIL_MAX_GAMES
11459                                 - gn - 1] = CMAIL_OLD_RESULT;
11460                 }
11461             }
11462             break;
11463
11464           case NormalMove:
11465             /* Only a NormalMove can be at the start of a game
11466              * without a position diagram. */
11467             if (lastLoadGameStart == EndOfFile ) {
11468               gn--;
11469               lastLoadGameStart = MoveNumberOne;
11470             }
11471             break;
11472
11473           default:
11474             break;
11475         }
11476     }
11477
11478     if (appData.debugMode)
11479       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11480
11481     if (cm == XBoardGame) {
11482         /* Skip any header junk before position diagram and/or move 1 */
11483         for (;;) {
11484             yyboardindex = forwardMostMove;
11485             cm = (ChessMove) Myylex();
11486
11487             if (cm == EndOfFile ||
11488                 cm == GNUChessGame || cm == XBoardGame) {
11489                 /* Empty game; pretend end-of-file and handle later */
11490                 cm = EndOfFile;
11491                 break;
11492             }
11493
11494             if (cm == MoveNumberOne || cm == PositionDiagram ||
11495                 cm == PGNTag || cm == Comment)
11496               break;
11497         }
11498     } else if (cm == GNUChessGame) {
11499         if (gameInfo.event != NULL) {
11500             free(gameInfo.event);
11501         }
11502         gameInfo.event = StrSave(yy_text);
11503     }
11504
11505     startedFromSetupPosition = FALSE;
11506     while (cm == PGNTag) {
11507         if (appData.debugMode)
11508           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11509         err = ParsePGNTag(yy_text, &gameInfo);
11510         if (!err) numPGNTags++;
11511
11512         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11513         if(gameInfo.variant != oldVariant) {
11514             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11515             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11516             InitPosition(TRUE);
11517             oldVariant = gameInfo.variant;
11518             if (appData.debugMode)
11519               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11520         }
11521
11522
11523         if (gameInfo.fen != NULL) {
11524           Board initial_position;
11525           startedFromSetupPosition = TRUE;
11526           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11527             Reset(TRUE, TRUE);
11528             DisplayError(_("Bad FEN position in file"), 0);
11529             return FALSE;
11530           }
11531           CopyBoard(boards[0], initial_position);
11532           if (blackPlaysFirst) {
11533             currentMove = forwardMostMove = backwardMostMove = 1;
11534             CopyBoard(boards[1], initial_position);
11535             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11536             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11537             timeRemaining[0][1] = whiteTimeRemaining;
11538             timeRemaining[1][1] = blackTimeRemaining;
11539             if (commentList[0] != NULL) {
11540               commentList[1] = commentList[0];
11541               commentList[0] = NULL;
11542             }
11543           } else {
11544             currentMove = forwardMostMove = backwardMostMove = 0;
11545           }
11546           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11547           {   int i;
11548               initialRulePlies = FENrulePlies;
11549               for( i=0; i< nrCastlingRights; i++ )
11550                   initialRights[i] = initial_position[CASTLING][i];
11551           }
11552           yyboardindex = forwardMostMove;
11553           free(gameInfo.fen);
11554           gameInfo.fen = NULL;
11555         }
11556
11557         yyboardindex = forwardMostMove;
11558         cm = (ChessMove) Myylex();
11559
11560         /* Handle comments interspersed among the tags */
11561         while (cm == Comment) {
11562             char *p;
11563             if (appData.debugMode)
11564               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11565             p = yy_text;
11566             AppendComment(currentMove, p, FALSE);
11567             yyboardindex = forwardMostMove;
11568             cm = (ChessMove) Myylex();
11569         }
11570     }
11571
11572     /* don't rely on existence of Event tag since if game was
11573      * pasted from clipboard the Event tag may not exist
11574      */
11575     if (numPGNTags > 0){
11576         char *tags;
11577         if (gameInfo.variant == VariantNormal) {
11578           VariantClass v = StringToVariant(gameInfo.event);
11579           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11580           if(v < VariantShogi) gameInfo.variant = v;
11581         }
11582         if (!matchMode) {
11583           if( appData.autoDisplayTags ) {
11584             tags = PGNTags(&gameInfo);
11585             TagsPopUp(tags, CmailMsg());
11586             free(tags);
11587           }
11588         }
11589     } else {
11590         /* Make something up, but don't display it now */
11591         SetGameInfo();
11592         TagsPopDown();
11593     }
11594
11595     if (cm == PositionDiagram) {
11596         int i, j;
11597         char *p;
11598         Board initial_position;
11599
11600         if (appData.debugMode)
11601           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11602
11603         if (!startedFromSetupPosition) {
11604             p = yy_text;
11605             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11606               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11607                 switch (*p) {
11608                   case '{':
11609                   case '[':
11610                   case '-':
11611                   case ' ':
11612                   case '\t':
11613                   case '\n':
11614                   case '\r':
11615                     break;
11616                   default:
11617                     initial_position[i][j++] = CharToPiece(*p);
11618                     break;
11619                 }
11620             while (*p == ' ' || *p == '\t' ||
11621                    *p == '\n' || *p == '\r') p++;
11622
11623             if (strncmp(p, "black", strlen("black"))==0)
11624               blackPlaysFirst = TRUE;
11625             else
11626               blackPlaysFirst = FALSE;
11627             startedFromSetupPosition = TRUE;
11628
11629             CopyBoard(boards[0], initial_position);
11630             if (blackPlaysFirst) {
11631                 currentMove = forwardMostMove = backwardMostMove = 1;
11632                 CopyBoard(boards[1], initial_position);
11633                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11634                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11635                 timeRemaining[0][1] = whiteTimeRemaining;
11636                 timeRemaining[1][1] = blackTimeRemaining;
11637                 if (commentList[0] != NULL) {
11638                     commentList[1] = commentList[0];
11639                     commentList[0] = NULL;
11640                 }
11641             } else {
11642                 currentMove = forwardMostMove = backwardMostMove = 0;
11643             }
11644         }
11645         yyboardindex = forwardMostMove;
11646         cm = (ChessMove) Myylex();
11647     }
11648
11649     if (first.pr == NoProc) {
11650         StartChessProgram(&first);
11651     }
11652     InitChessProgram(&first, FALSE);
11653     SendToProgram("force\n", &first);
11654     if (startedFromSetupPosition) {
11655         SendBoard(&first, forwardMostMove);
11656     if (appData.debugMode) {
11657         fprintf(debugFP, "Load Game\n");
11658     }
11659         DisplayBothClocks();
11660     }
11661
11662     /* [HGM] server: flag to write setup moves in broadcast file as one */
11663     loadFlag = appData.suppressLoadMoves;
11664
11665     while (cm == Comment) {
11666         char *p;
11667         if (appData.debugMode)
11668           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11669         p = yy_text;
11670         AppendComment(currentMove, p, FALSE);
11671         yyboardindex = forwardMostMove;
11672         cm = (ChessMove) Myylex();
11673     }
11674
11675     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11676         cm == WhiteWins || cm == BlackWins ||
11677         cm == GameIsDrawn || cm == GameUnfinished) {
11678         DisplayMessage("", _("No moves in game"));
11679         if (cmailMsgLoaded) {
11680             if (appData.debugMode)
11681               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11682             ClearHighlights();
11683             flipView = FALSE;
11684         }
11685         DrawPosition(FALSE, boards[currentMove]);
11686         DisplayBothClocks();
11687         gameMode = EditGame;
11688         ModeHighlight();
11689         gameFileFP = NULL;
11690         cmailOldMove = 0;
11691         return TRUE;
11692     }
11693
11694     // [HGM] PV info: routine tests if comment empty
11695     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11696         DisplayComment(currentMove - 1, commentList[currentMove]);
11697     }
11698     if (!matchMode && appData.timeDelay != 0)
11699       DrawPosition(FALSE, boards[currentMove]);
11700
11701     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11702       programStats.ok_to_send = 1;
11703     }
11704
11705     /* if the first token after the PGN tags is a move
11706      * and not move number 1, retrieve it from the parser
11707      */
11708     if (cm != MoveNumberOne)
11709         LoadGameOneMove(cm);
11710
11711     /* load the remaining moves from the file */
11712     while (LoadGameOneMove(EndOfFile)) {
11713       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11714       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11715     }
11716
11717     /* rewind to the start of the game */
11718     currentMove = backwardMostMove;
11719
11720     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11721
11722     if (oldGameMode == AnalyzeFile ||
11723         oldGameMode == AnalyzeMode) {
11724       AnalyzeFileEvent();
11725     }
11726
11727     if (!matchMode && pos >= 0) {
11728         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11729     } else
11730     if (matchMode || appData.timeDelay == 0) {
11731       ToEndEvent();
11732     } else if (appData.timeDelay > 0) {
11733       AutoPlayGameLoop();
11734     }
11735
11736     if (appData.debugMode)
11737         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11738
11739     loadFlag = 0; /* [HGM] true game starts */
11740     return TRUE;
11741 }
11742
11743 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11744 int
11745 ReloadPosition(offset)
11746      int offset;
11747 {
11748     int positionNumber = lastLoadPositionNumber + offset;
11749     if (lastLoadPositionFP == NULL) {
11750         DisplayError(_("No position has been loaded yet"), 0);
11751         return FALSE;
11752     }
11753     if (positionNumber <= 0) {
11754         DisplayError(_("Can't back up any further"), 0);
11755         return FALSE;
11756     }
11757     return LoadPosition(lastLoadPositionFP, positionNumber,
11758                         lastLoadPositionTitle);
11759 }
11760
11761 /* Load the nth position from the given file */
11762 int
11763 LoadPositionFromFile(filename, n, title)
11764      char *filename;
11765      int n;
11766      char *title;
11767 {
11768     FILE *f;
11769     char buf[MSG_SIZ];
11770
11771     if (strcmp(filename, "-") == 0) {
11772         return LoadPosition(stdin, n, "stdin");
11773     } else {
11774         f = fopen(filename, "rb");
11775         if (f == NULL) {
11776             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11777             DisplayError(buf, errno);
11778             return FALSE;
11779         } else {
11780             return LoadPosition(f, n, title);
11781         }
11782     }
11783 }
11784
11785 /* Load the nth position from the given open file, and close it */
11786 int
11787 LoadPosition(f, positionNumber, title)
11788      FILE *f;
11789      int positionNumber;
11790      char *title;
11791 {
11792     char *p, line[MSG_SIZ];
11793     Board initial_position;
11794     int i, j, fenMode, pn;
11795
11796     if (gameMode == Training )
11797         SetTrainingModeOff();
11798
11799     if (gameMode != BeginningOfGame) {
11800         Reset(FALSE, TRUE);
11801     }
11802     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11803         fclose(lastLoadPositionFP);
11804     }
11805     if (positionNumber == 0) positionNumber = 1;
11806     lastLoadPositionFP = f;
11807     lastLoadPositionNumber = positionNumber;
11808     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11809     if (first.pr == NoProc && !appData.noChessProgram) {
11810       StartChessProgram(&first);
11811       InitChessProgram(&first, FALSE);
11812     }
11813     pn = positionNumber;
11814     if (positionNumber < 0) {
11815         /* Negative position number means to seek to that byte offset */
11816         if (fseek(f, -positionNumber, 0) == -1) {
11817             DisplayError(_("Can't seek on position file"), 0);
11818             return FALSE;
11819         };
11820         pn = 1;
11821     } else {
11822         if (fseek(f, 0, 0) == -1) {
11823             if (f == lastLoadPositionFP ?
11824                 positionNumber == lastLoadPositionNumber + 1 :
11825                 positionNumber == 1) {
11826                 pn = 1;
11827             } else {
11828                 DisplayError(_("Can't seek on position file"), 0);
11829                 return FALSE;
11830             }
11831         }
11832     }
11833     /* See if this file is FEN or old-style xboard */
11834     if (fgets(line, MSG_SIZ, f) == NULL) {
11835         DisplayError(_("Position not found in file"), 0);
11836         return FALSE;
11837     }
11838     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11839     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11840
11841     if (pn >= 2) {
11842         if (fenMode || line[0] == '#') pn--;
11843         while (pn > 0) {
11844             /* skip positions before number pn */
11845             if (fgets(line, MSG_SIZ, f) == NULL) {
11846                 Reset(TRUE, TRUE);
11847                 DisplayError(_("Position not found in file"), 0);
11848                 return FALSE;
11849             }
11850             if (fenMode || line[0] == '#') pn--;
11851         }
11852     }
11853
11854     if (fenMode) {
11855         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11856             DisplayError(_("Bad FEN position in file"), 0);
11857             return FALSE;
11858         }
11859     } else {
11860         (void) fgets(line, MSG_SIZ, f);
11861         (void) fgets(line, MSG_SIZ, f);
11862
11863         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11864             (void) fgets(line, MSG_SIZ, f);
11865             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11866                 if (*p == ' ')
11867                   continue;
11868                 initial_position[i][j++] = CharToPiece(*p);
11869             }
11870         }
11871
11872         blackPlaysFirst = FALSE;
11873         if (!feof(f)) {
11874             (void) fgets(line, MSG_SIZ, f);
11875             if (strncmp(line, "black", strlen("black"))==0)
11876               blackPlaysFirst = TRUE;
11877         }
11878     }
11879     startedFromSetupPosition = TRUE;
11880
11881     CopyBoard(boards[0], initial_position);
11882     if (blackPlaysFirst) {
11883         currentMove = forwardMostMove = backwardMostMove = 1;
11884         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11885         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11886         CopyBoard(boards[1], initial_position);
11887         DisplayMessage("", _("Black to play"));
11888     } else {
11889         currentMove = forwardMostMove = backwardMostMove = 0;
11890         DisplayMessage("", _("White to play"));
11891     }
11892     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11893     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
11894         SendToProgram("force\n", &first);
11895         SendBoard(&first, forwardMostMove);
11896     }
11897     if (appData.debugMode) {
11898 int i, j;
11899   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11900   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11901         fprintf(debugFP, "Load Position\n");
11902     }
11903
11904     if (positionNumber > 1) {
11905       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11906         DisplayTitle(line);
11907     } else {
11908         DisplayTitle(title);
11909     }
11910     gameMode = EditGame;
11911     ModeHighlight();
11912     ResetClocks();
11913     timeRemaining[0][1] = whiteTimeRemaining;
11914     timeRemaining[1][1] = blackTimeRemaining;
11915     DrawPosition(FALSE, boards[currentMove]);
11916
11917     return TRUE;
11918 }
11919
11920
11921 void
11922 CopyPlayerNameIntoFileName(dest, src)
11923      char **dest, *src;
11924 {
11925     while (*src != NULLCHAR && *src != ',') {
11926         if (*src == ' ') {
11927             *(*dest)++ = '_';
11928             src++;
11929         } else {
11930             *(*dest)++ = *src++;
11931         }
11932     }
11933 }
11934
11935 char *DefaultFileName(ext)
11936      char *ext;
11937 {
11938     static char def[MSG_SIZ];
11939     char *p;
11940
11941     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11942         p = def;
11943         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11944         *p++ = '-';
11945         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11946         *p++ = '.';
11947         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11948     } else {
11949         def[0] = NULLCHAR;
11950     }
11951     return def;
11952 }
11953
11954 /* Save the current game to the given file */
11955 int
11956 SaveGameToFile(filename, append)
11957      char *filename;
11958      int append;
11959 {
11960     FILE *f;
11961     char buf[MSG_SIZ];
11962     int result, i, t,tot=0;
11963
11964     if (strcmp(filename, "-") == 0) {
11965         return SaveGame(stdout, 0, NULL);
11966     } else {
11967         for(i=0; i<10; i++) { // upto 10 tries
11968              f = fopen(filename, append ? "a" : "w");
11969              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
11970              if(f || errno != 13) break;
11971              DoSleep(t = 5 + random()%11); // wait 5-15 msec
11972              tot += t;
11973         }
11974         if (f == NULL) {
11975             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11976             DisplayError(buf, errno);
11977             return FALSE;
11978         } else {
11979             safeStrCpy(buf, lastMsg, MSG_SIZ);
11980             DisplayMessage(_("Waiting for access to save file"), "");
11981             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11982             DisplayMessage(_("Saving game"), "");
11983             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11984             result = SaveGame(f, 0, NULL);
11985             DisplayMessage(buf, "");
11986             return result;
11987         }
11988     }
11989 }
11990
11991 char *
11992 SavePart(str)
11993      char *str;
11994 {
11995     static char buf[MSG_SIZ];
11996     char *p;
11997
11998     p = strchr(str, ' ');
11999     if (p == NULL) return str;
12000     strncpy(buf, str, p - str);
12001     buf[p - str] = NULLCHAR;
12002     return buf;
12003 }
12004
12005 #define PGN_MAX_LINE 75
12006
12007 #define PGN_SIDE_WHITE  0
12008 #define PGN_SIDE_BLACK  1
12009
12010 /* [AS] */
12011 static int FindFirstMoveOutOfBook( int side )
12012 {
12013     int result = -1;
12014
12015     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12016         int index = backwardMostMove;
12017         int has_book_hit = 0;
12018
12019         if( (index % 2) != side ) {
12020             index++;
12021         }
12022
12023         while( index < forwardMostMove ) {
12024             /* Check to see if engine is in book */
12025             int depth = pvInfoList[index].depth;
12026             int score = pvInfoList[index].score;
12027             int in_book = 0;
12028
12029             if( depth <= 2 ) {
12030                 in_book = 1;
12031             }
12032             else if( score == 0 && depth == 63 ) {
12033                 in_book = 1; /* Zappa */
12034             }
12035             else if( score == 2 && depth == 99 ) {
12036                 in_book = 1; /* Abrok */
12037             }
12038
12039             has_book_hit += in_book;
12040
12041             if( ! in_book ) {
12042                 result = index;
12043
12044                 break;
12045             }
12046
12047             index += 2;
12048         }
12049     }
12050
12051     return result;
12052 }
12053
12054 /* [AS] */
12055 void GetOutOfBookInfo( char * buf )
12056 {
12057     int oob[2];
12058     int i;
12059     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12060
12061     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12062     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12063
12064     *buf = '\0';
12065
12066     if( oob[0] >= 0 || oob[1] >= 0 ) {
12067         for( i=0; i<2; i++ ) {
12068             int idx = oob[i];
12069
12070             if( idx >= 0 ) {
12071                 if( i > 0 && oob[0] >= 0 ) {
12072                     strcat( buf, "   " );
12073                 }
12074
12075                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12076                 sprintf( buf+strlen(buf), "%s%.2f",
12077                     pvInfoList[idx].score >= 0 ? "+" : "",
12078                     pvInfoList[idx].score / 100.0 );
12079             }
12080         }
12081     }
12082 }
12083
12084 /* Save game in PGN style and close the file */
12085 int
12086 SaveGamePGN(f)
12087      FILE *f;
12088 {
12089     int i, offset, linelen, newblock;
12090     time_t tm;
12091 //    char *movetext;
12092     char numtext[32];
12093     int movelen, numlen, blank;
12094     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12095
12096     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12097
12098     tm = time((time_t *) NULL);
12099
12100     PrintPGNTags(f, &gameInfo);
12101
12102     if (backwardMostMove > 0 || startedFromSetupPosition) {
12103         char *fen = PositionToFEN(backwardMostMove, NULL);
12104         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12105         fprintf(f, "\n{--------------\n");
12106         PrintPosition(f, backwardMostMove);
12107         fprintf(f, "--------------}\n");
12108         free(fen);
12109     }
12110     else {
12111         /* [AS] Out of book annotation */
12112         if( appData.saveOutOfBookInfo ) {
12113             char buf[64];
12114
12115             GetOutOfBookInfo( buf );
12116
12117             if( buf[0] != '\0' ) {
12118                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12119             }
12120         }
12121
12122         fprintf(f, "\n");
12123     }
12124
12125     i = backwardMostMove;
12126     linelen = 0;
12127     newblock = TRUE;
12128
12129     while (i < forwardMostMove) {
12130         /* Print comments preceding this move */
12131         if (commentList[i] != NULL) {
12132             if (linelen > 0) fprintf(f, "\n");
12133             fprintf(f, "%s", commentList[i]);
12134             linelen = 0;
12135             newblock = TRUE;
12136         }
12137
12138         /* Format move number */
12139         if ((i % 2) == 0)
12140           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12141         else
12142           if (newblock)
12143             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12144           else
12145             numtext[0] = NULLCHAR;
12146
12147         numlen = strlen(numtext);
12148         newblock = FALSE;
12149
12150         /* Print move number */
12151         blank = linelen > 0 && numlen > 0;
12152         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12153             fprintf(f, "\n");
12154             linelen = 0;
12155             blank = 0;
12156         }
12157         if (blank) {
12158             fprintf(f, " ");
12159             linelen++;
12160         }
12161         fprintf(f, "%s", numtext);
12162         linelen += numlen;
12163
12164         /* Get move */
12165         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12166         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12167
12168         /* Print move */
12169         blank = linelen > 0 && movelen > 0;
12170         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12171             fprintf(f, "\n");
12172             linelen = 0;
12173             blank = 0;
12174         }
12175         if (blank) {
12176             fprintf(f, " ");
12177             linelen++;
12178         }
12179         fprintf(f, "%s", move_buffer);
12180         linelen += movelen;
12181
12182         /* [AS] Add PV info if present */
12183         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12184             /* [HGM] add time */
12185             char buf[MSG_SIZ]; int seconds;
12186
12187             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12188
12189             if( seconds <= 0)
12190               buf[0] = 0;
12191             else
12192               if( seconds < 30 )
12193                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12194               else
12195                 {
12196                   seconds = (seconds + 4)/10; // round to full seconds
12197                   if( seconds < 60 )
12198                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12199                   else
12200                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12201                 }
12202
12203             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12204                       pvInfoList[i].score >= 0 ? "+" : "",
12205                       pvInfoList[i].score / 100.0,
12206                       pvInfoList[i].depth,
12207                       buf );
12208
12209             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12210
12211             /* Print score/depth */
12212             blank = linelen > 0 && movelen > 0;
12213             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12214                 fprintf(f, "\n");
12215                 linelen = 0;
12216                 blank = 0;
12217             }
12218             if (blank) {
12219                 fprintf(f, " ");
12220                 linelen++;
12221             }
12222             fprintf(f, "%s", move_buffer);
12223             linelen += movelen;
12224         }
12225
12226         i++;
12227     }
12228
12229     /* Start a new line */
12230     if (linelen > 0) fprintf(f, "\n");
12231
12232     /* Print comments after last move */
12233     if (commentList[i] != NULL) {
12234         fprintf(f, "%s\n", commentList[i]);
12235     }
12236
12237     /* Print result */
12238     if (gameInfo.resultDetails != NULL &&
12239         gameInfo.resultDetails[0] != NULLCHAR) {
12240         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12241                 PGNResult(gameInfo.result));
12242     } else {
12243         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12244     }
12245
12246     fclose(f);
12247     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12248     return TRUE;
12249 }
12250
12251 /* Save game in old style and close the file */
12252 int
12253 SaveGameOldStyle(f)
12254      FILE *f;
12255 {
12256     int i, offset;
12257     time_t tm;
12258
12259     tm = time((time_t *) NULL);
12260
12261     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12262     PrintOpponents(f);
12263
12264     if (backwardMostMove > 0 || startedFromSetupPosition) {
12265         fprintf(f, "\n[--------------\n");
12266         PrintPosition(f, backwardMostMove);
12267         fprintf(f, "--------------]\n");
12268     } else {
12269         fprintf(f, "\n");
12270     }
12271
12272     i = backwardMostMove;
12273     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12274
12275     while (i < forwardMostMove) {
12276         if (commentList[i] != NULL) {
12277             fprintf(f, "[%s]\n", commentList[i]);
12278         }
12279
12280         if ((i % 2) == 1) {
12281             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12282             i++;
12283         } else {
12284             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12285             i++;
12286             if (commentList[i] != NULL) {
12287                 fprintf(f, "\n");
12288                 continue;
12289             }
12290             if (i >= forwardMostMove) {
12291                 fprintf(f, "\n");
12292                 break;
12293             }
12294             fprintf(f, "%s\n", parseList[i]);
12295             i++;
12296         }
12297     }
12298
12299     if (commentList[i] != NULL) {
12300         fprintf(f, "[%s]\n", commentList[i]);
12301     }
12302
12303     /* This isn't really the old style, but it's close enough */
12304     if (gameInfo.resultDetails != NULL &&
12305         gameInfo.resultDetails[0] != NULLCHAR) {
12306         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12307                 gameInfo.resultDetails);
12308     } else {
12309         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12310     }
12311
12312     fclose(f);
12313     return TRUE;
12314 }
12315
12316 /* Save the current game to open file f and close the file */
12317 int
12318 SaveGame(f, dummy, dummy2)
12319      FILE *f;
12320      int dummy;
12321      char *dummy2;
12322 {
12323     if (gameMode == EditPosition) EditPositionDone(TRUE);
12324     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12325     if (appData.oldSaveStyle)
12326       return SaveGameOldStyle(f);
12327     else
12328       return SaveGamePGN(f);
12329 }
12330
12331 /* Save the current position to the given file */
12332 int
12333 SavePositionToFile(filename)
12334      char *filename;
12335 {
12336     FILE *f;
12337     char buf[MSG_SIZ];
12338
12339     if (strcmp(filename, "-") == 0) {
12340         return SavePosition(stdout, 0, NULL);
12341     } else {
12342         f = fopen(filename, "a");
12343         if (f == NULL) {
12344             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12345             DisplayError(buf, errno);
12346             return FALSE;
12347         } else {
12348             safeStrCpy(buf, lastMsg, MSG_SIZ);
12349             DisplayMessage(_("Waiting for access to save file"), "");
12350             flock(fileno(f), LOCK_EX); // [HGM] lock
12351             DisplayMessage(_("Saving position"), "");
12352             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12353             SavePosition(f, 0, NULL);
12354             DisplayMessage(buf, "");
12355             return TRUE;
12356         }
12357     }
12358 }
12359
12360 /* Save the current position to the given open file and close the file */
12361 int
12362 SavePosition(f, dummy, dummy2)
12363      FILE *f;
12364      int dummy;
12365      char *dummy2;
12366 {
12367     time_t tm;
12368     char *fen;
12369
12370     if (gameMode == EditPosition) EditPositionDone(TRUE);
12371     if (appData.oldSaveStyle) {
12372         tm = time((time_t *) NULL);
12373
12374         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12375         PrintOpponents(f);
12376         fprintf(f, "[--------------\n");
12377         PrintPosition(f, currentMove);
12378         fprintf(f, "--------------]\n");
12379     } else {
12380         fen = PositionToFEN(currentMove, NULL);
12381         fprintf(f, "%s\n", fen);
12382         free(fen);
12383     }
12384     fclose(f);
12385     return TRUE;
12386 }
12387
12388 void
12389 ReloadCmailMsgEvent(unregister)
12390      int unregister;
12391 {
12392 #if !WIN32
12393     static char *inFilename = NULL;
12394     static char *outFilename;
12395     int i;
12396     struct stat inbuf, outbuf;
12397     int status;
12398
12399     /* Any registered moves are unregistered if unregister is set, */
12400     /* i.e. invoked by the signal handler */
12401     if (unregister) {
12402         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12403             cmailMoveRegistered[i] = FALSE;
12404             if (cmailCommentList[i] != NULL) {
12405                 free(cmailCommentList[i]);
12406                 cmailCommentList[i] = NULL;
12407             }
12408         }
12409         nCmailMovesRegistered = 0;
12410     }
12411
12412     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12413         cmailResult[i] = CMAIL_NOT_RESULT;
12414     }
12415     nCmailResults = 0;
12416
12417     if (inFilename == NULL) {
12418         /* Because the filenames are static they only get malloced once  */
12419         /* and they never get freed                                      */
12420         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12421         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12422
12423         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12424         sprintf(outFilename, "%s.out", appData.cmailGameName);
12425     }
12426
12427     status = stat(outFilename, &outbuf);
12428     if (status < 0) {
12429         cmailMailedMove = FALSE;
12430     } else {
12431         status = stat(inFilename, &inbuf);
12432         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12433     }
12434
12435     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12436        counts the games, notes how each one terminated, etc.
12437
12438        It would be nice to remove this kludge and instead gather all
12439        the information while building the game list.  (And to keep it
12440        in the game list nodes instead of having a bunch of fixed-size
12441        parallel arrays.)  Note this will require getting each game's
12442        termination from the PGN tags, as the game list builder does
12443        not process the game moves.  --mann
12444        */
12445     cmailMsgLoaded = TRUE;
12446     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12447
12448     /* Load first game in the file or popup game menu */
12449     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12450
12451 #endif /* !WIN32 */
12452     return;
12453 }
12454
12455 int
12456 RegisterMove()
12457 {
12458     FILE *f;
12459     char string[MSG_SIZ];
12460
12461     if (   cmailMailedMove
12462         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12463         return TRUE;            /* Allow free viewing  */
12464     }
12465
12466     /* Unregister move to ensure that we don't leave RegisterMove        */
12467     /* with the move registered when the conditions for registering no   */
12468     /* longer hold                                                       */
12469     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12470         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12471         nCmailMovesRegistered --;
12472
12473         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12474           {
12475               free(cmailCommentList[lastLoadGameNumber - 1]);
12476               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12477           }
12478     }
12479
12480     if (cmailOldMove == -1) {
12481         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12482         return FALSE;
12483     }
12484
12485     if (currentMove > cmailOldMove + 1) {
12486         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12487         return FALSE;
12488     }
12489
12490     if (currentMove < cmailOldMove) {
12491         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12492         return FALSE;
12493     }
12494
12495     if (forwardMostMove > currentMove) {
12496         /* Silently truncate extra moves */
12497         TruncateGame();
12498     }
12499
12500     if (   (currentMove == cmailOldMove + 1)
12501         || (   (currentMove == cmailOldMove)
12502             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12503                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12504         if (gameInfo.result != GameUnfinished) {
12505             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12506         }
12507
12508         if (commentList[currentMove] != NULL) {
12509             cmailCommentList[lastLoadGameNumber - 1]
12510               = StrSave(commentList[currentMove]);
12511         }
12512         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12513
12514         if (appData.debugMode)
12515           fprintf(debugFP, "Saving %s for game %d\n",
12516                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12517
12518         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12519
12520         f = fopen(string, "w");
12521         if (appData.oldSaveStyle) {
12522             SaveGameOldStyle(f); /* also closes the file */
12523
12524             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12525             f = fopen(string, "w");
12526             SavePosition(f, 0, NULL); /* also closes the file */
12527         } else {
12528             fprintf(f, "{--------------\n");
12529             PrintPosition(f, currentMove);
12530             fprintf(f, "--------------}\n\n");
12531
12532             SaveGame(f, 0, NULL); /* also closes the file*/
12533         }
12534
12535         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12536         nCmailMovesRegistered ++;
12537     } else if (nCmailGames == 1) {
12538         DisplayError(_("You have not made a move yet"), 0);
12539         return FALSE;
12540     }
12541
12542     return TRUE;
12543 }
12544
12545 void
12546 MailMoveEvent()
12547 {
12548 #if !WIN32
12549     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12550     FILE *commandOutput;
12551     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12552     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12553     int nBuffers;
12554     int i;
12555     int archived;
12556     char *arcDir;
12557
12558     if (! cmailMsgLoaded) {
12559         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12560         return;
12561     }
12562
12563     if (nCmailGames == nCmailResults) {
12564         DisplayError(_("No unfinished games"), 0);
12565         return;
12566     }
12567
12568 #if CMAIL_PROHIBIT_REMAIL
12569     if (cmailMailedMove) {
12570       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);
12571         DisplayError(msg, 0);
12572         return;
12573     }
12574 #endif
12575
12576     if (! (cmailMailedMove || RegisterMove())) return;
12577
12578     if (   cmailMailedMove
12579         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12580       snprintf(string, MSG_SIZ, partCommandString,
12581                appData.debugMode ? " -v" : "", appData.cmailGameName);
12582         commandOutput = popen(string, "r");
12583
12584         if (commandOutput == NULL) {
12585             DisplayError(_("Failed to invoke cmail"), 0);
12586         } else {
12587             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12588                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12589             }
12590             if (nBuffers > 1) {
12591                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12592                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12593                 nBytes = MSG_SIZ - 1;
12594             } else {
12595                 (void) memcpy(msg, buffer, nBytes);
12596             }
12597             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12598
12599             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12600                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12601
12602                 archived = TRUE;
12603                 for (i = 0; i < nCmailGames; i ++) {
12604                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12605                         archived = FALSE;
12606                     }
12607                 }
12608                 if (   archived
12609                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12610                         != NULL)) {
12611                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12612                            arcDir,
12613                            appData.cmailGameName,
12614                            gameInfo.date);
12615                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12616                     cmailMsgLoaded = FALSE;
12617                 }
12618             }
12619
12620             DisplayInformation(msg);
12621             pclose(commandOutput);
12622         }
12623     } else {
12624         if ((*cmailMsg) != '\0') {
12625             DisplayInformation(cmailMsg);
12626         }
12627     }
12628
12629     return;
12630 #endif /* !WIN32 */
12631 }
12632
12633 char *
12634 CmailMsg()
12635 {
12636 #if WIN32
12637     return NULL;
12638 #else
12639     int  prependComma = 0;
12640     char number[5];
12641     char string[MSG_SIZ];       /* Space for game-list */
12642     int  i;
12643
12644     if (!cmailMsgLoaded) return "";
12645
12646     if (cmailMailedMove) {
12647       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12648     } else {
12649         /* Create a list of games left */
12650       snprintf(string, MSG_SIZ, "[");
12651         for (i = 0; i < nCmailGames; i ++) {
12652             if (! (   cmailMoveRegistered[i]
12653                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12654                 if (prependComma) {
12655                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12656                 } else {
12657                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12658                     prependComma = 1;
12659                 }
12660
12661                 strcat(string, number);
12662             }
12663         }
12664         strcat(string, "]");
12665
12666         if (nCmailMovesRegistered + nCmailResults == 0) {
12667             switch (nCmailGames) {
12668               case 1:
12669                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12670                 break;
12671
12672               case 2:
12673                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12674                 break;
12675
12676               default:
12677                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12678                          nCmailGames);
12679                 break;
12680             }
12681         } else {
12682             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12683               case 1:
12684                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12685                          string);
12686                 break;
12687
12688               case 0:
12689                 if (nCmailResults == nCmailGames) {
12690                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12691                 } else {
12692                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12693                 }
12694                 break;
12695
12696               default:
12697                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12698                          string);
12699             }
12700         }
12701     }
12702     return cmailMsg;
12703 #endif /* WIN32 */
12704 }
12705
12706 void
12707 ResetGameEvent()
12708 {
12709     if (gameMode == Training)
12710       SetTrainingModeOff();
12711
12712     Reset(TRUE, TRUE);
12713     cmailMsgLoaded = FALSE;
12714     if (appData.icsActive) {
12715       SendToICS(ics_prefix);
12716       SendToICS("refresh\n");
12717     }
12718 }
12719
12720 void
12721 ExitEvent(status)
12722      int status;
12723 {
12724     exiting++;
12725     if (exiting > 2) {
12726       /* Give up on clean exit */
12727       exit(status);
12728     }
12729     if (exiting > 1) {
12730       /* Keep trying for clean exit */
12731       return;
12732     }
12733
12734     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12735
12736     if (telnetISR != NULL) {
12737       RemoveInputSource(telnetISR);
12738     }
12739     if (icsPR != NoProc) {
12740       DestroyChildProcess(icsPR, TRUE);
12741     }
12742
12743     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12744     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12745
12746     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12747     /* make sure this other one finishes before killing it!                  */
12748     if(endingGame) { int count = 0;
12749         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12750         while(endingGame && count++ < 10) DoSleep(1);
12751         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12752     }
12753
12754     /* Kill off chess programs */
12755     if (first.pr != NoProc) {
12756         ExitAnalyzeMode();
12757
12758         DoSleep( appData.delayBeforeQuit );
12759         SendToProgram("quit\n", &first);
12760         DoSleep( appData.delayAfterQuit );
12761         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12762     }
12763     if (second.pr != NoProc) {
12764         DoSleep( appData.delayBeforeQuit );
12765         SendToProgram("quit\n", &second);
12766         DoSleep( appData.delayAfterQuit );
12767         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12768     }
12769     if (first.isr != NULL) {
12770         RemoveInputSource(first.isr);
12771     }
12772     if (second.isr != NULL) {
12773         RemoveInputSource(second.isr);
12774     }
12775
12776     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12777     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12778
12779     ShutDownFrontEnd();
12780     exit(status);
12781 }
12782
12783 void
12784 PauseEvent()
12785 {
12786     if (appData.debugMode)
12787         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12788     if (pausing) {
12789         pausing = FALSE;
12790         ModeHighlight();
12791         if (gameMode == MachinePlaysWhite ||
12792             gameMode == MachinePlaysBlack) {
12793             StartClocks();
12794         } else {
12795             DisplayBothClocks();
12796         }
12797         if (gameMode == PlayFromGameFile) {
12798             if (appData.timeDelay >= 0)
12799                 AutoPlayGameLoop();
12800         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12801             Reset(FALSE, TRUE);
12802             SendToICS(ics_prefix);
12803             SendToICS("refresh\n");
12804         } else if (currentMove < forwardMostMove) {
12805             ForwardInner(forwardMostMove);
12806         }
12807         pauseExamInvalid = FALSE;
12808     } else {
12809         switch (gameMode) {
12810           default:
12811             return;
12812           case IcsExamining:
12813             pauseExamForwardMostMove = forwardMostMove;
12814             pauseExamInvalid = FALSE;
12815             /* fall through */
12816           case IcsObserving:
12817           case IcsPlayingWhite:
12818           case IcsPlayingBlack:
12819             pausing = TRUE;
12820             ModeHighlight();
12821             return;
12822           case PlayFromGameFile:
12823             (void) StopLoadGameTimer();
12824             pausing = TRUE;
12825             ModeHighlight();
12826             break;
12827           case BeginningOfGame:
12828             if (appData.icsActive) return;
12829             /* else fall through */
12830           case MachinePlaysWhite:
12831           case MachinePlaysBlack:
12832           case TwoMachinesPlay:
12833             if (forwardMostMove == 0)
12834               return;           /* don't pause if no one has moved */
12835             if ((gameMode == MachinePlaysWhite &&
12836                  !WhiteOnMove(forwardMostMove)) ||
12837                 (gameMode == MachinePlaysBlack &&
12838                  WhiteOnMove(forwardMostMove))) {
12839                 StopClocks();
12840             }
12841             pausing = TRUE;
12842             ModeHighlight();
12843             break;
12844         }
12845     }
12846 }
12847
12848 void
12849 EditCommentEvent()
12850 {
12851     char title[MSG_SIZ];
12852
12853     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12854       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12855     } else {
12856       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12857                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12858                parseList[currentMove - 1]);
12859     }
12860
12861     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12862 }
12863
12864
12865 void
12866 EditTagsEvent()
12867 {
12868     char *tags = PGNTags(&gameInfo);
12869     bookUp = FALSE;
12870     EditTagsPopUp(tags, NULL);
12871     free(tags);
12872 }
12873
12874 void
12875 AnalyzeModeEvent()
12876 {
12877     if (appData.noChessProgram || gameMode == AnalyzeMode)
12878       return;
12879
12880     if (gameMode != AnalyzeFile) {
12881         if (!appData.icsEngineAnalyze) {
12882                EditGameEvent();
12883                if (gameMode != EditGame) return;
12884         }
12885         ResurrectChessProgram();
12886         SendToProgram("analyze\n", &first);
12887         first.analyzing = TRUE;
12888         /*first.maybeThinking = TRUE;*/
12889         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12890         EngineOutputPopUp();
12891     }
12892     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12893     pausing = FALSE;
12894     ModeHighlight();
12895     SetGameInfo();
12896
12897     StartAnalysisClock();
12898     GetTimeMark(&lastNodeCountTime);
12899     lastNodeCount = 0;
12900 }
12901
12902 void
12903 AnalyzeFileEvent()
12904 {
12905     if (appData.noChessProgram || gameMode == AnalyzeFile)
12906       return;
12907
12908     if (gameMode != AnalyzeMode) {
12909         EditGameEvent();
12910         if (gameMode != EditGame) return;
12911         ResurrectChessProgram();
12912         SendToProgram("analyze\n", &first);
12913         first.analyzing = TRUE;
12914         /*first.maybeThinking = TRUE;*/
12915         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12916         EngineOutputPopUp();
12917     }
12918     gameMode = AnalyzeFile;
12919     pausing = FALSE;
12920     ModeHighlight();
12921     SetGameInfo();
12922
12923     StartAnalysisClock();
12924     GetTimeMark(&lastNodeCountTime);
12925     lastNodeCount = 0;
12926     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
12927 }
12928
12929 void
12930 MachineWhiteEvent()
12931 {
12932     char buf[MSG_SIZ];
12933     char *bookHit = NULL;
12934
12935     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12936       return;
12937
12938
12939     if (gameMode == PlayFromGameFile ||
12940         gameMode == TwoMachinesPlay  ||
12941         gameMode == Training         ||
12942         gameMode == AnalyzeMode      ||
12943         gameMode == EndOfGame)
12944         EditGameEvent();
12945
12946     if (gameMode == EditPosition)
12947         EditPositionDone(TRUE);
12948
12949     if (!WhiteOnMove(currentMove)) {
12950         DisplayError(_("It is not White's turn"), 0);
12951         return;
12952     }
12953
12954     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12955       ExitAnalyzeMode();
12956
12957     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12958         gameMode == AnalyzeFile)
12959         TruncateGame();
12960
12961     ResurrectChessProgram();    /* in case it isn't running */
12962     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12963         gameMode = MachinePlaysWhite;
12964         ResetClocks();
12965     } else
12966     gameMode = MachinePlaysWhite;
12967     pausing = FALSE;
12968     ModeHighlight();
12969     SetGameInfo();
12970     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12971     DisplayTitle(buf);
12972     if (first.sendName) {
12973       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12974       SendToProgram(buf, &first);
12975     }
12976     if (first.sendTime) {
12977       if (first.useColors) {
12978         SendToProgram("black\n", &first); /*gnu kludge*/
12979       }
12980       SendTimeRemaining(&first, TRUE);
12981     }
12982     if (first.useColors) {
12983       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12984     }
12985     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12986     SetMachineThinkingEnables();
12987     first.maybeThinking = TRUE;
12988     StartClocks();
12989     firstMove = FALSE;
12990
12991     if (appData.autoFlipView && !flipView) {
12992       flipView = !flipView;
12993       DrawPosition(FALSE, NULL);
12994       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12995     }
12996
12997     if(bookHit) { // [HGM] book: simulate book reply
12998         static char bookMove[MSG_SIZ]; // a bit generous?
12999
13000         programStats.nodes = programStats.depth = programStats.time =
13001         programStats.score = programStats.got_only_move = 0;
13002         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13003
13004         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13005         strcat(bookMove, bookHit);
13006         HandleMachineMove(bookMove, &first);
13007     }
13008 }
13009
13010 void
13011 MachineBlackEvent()
13012 {
13013   char buf[MSG_SIZ];
13014   char *bookHit = NULL;
13015
13016     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13017         return;
13018
13019
13020     if (gameMode == PlayFromGameFile ||
13021         gameMode == TwoMachinesPlay  ||
13022         gameMode == Training         ||
13023         gameMode == AnalyzeMode      ||
13024         gameMode == EndOfGame)
13025         EditGameEvent();
13026
13027     if (gameMode == EditPosition)
13028         EditPositionDone(TRUE);
13029
13030     if (WhiteOnMove(currentMove)) {
13031         DisplayError(_("It is not Black's turn"), 0);
13032         return;
13033     }
13034
13035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13036       ExitAnalyzeMode();
13037
13038     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13039         gameMode == AnalyzeFile)
13040         TruncateGame();
13041
13042     ResurrectChessProgram();    /* in case it isn't running */
13043     gameMode = MachinePlaysBlack;
13044     pausing = FALSE;
13045     ModeHighlight();
13046     SetGameInfo();
13047     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13048     DisplayTitle(buf);
13049     if (first.sendName) {
13050       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13051       SendToProgram(buf, &first);
13052     }
13053     if (first.sendTime) {
13054       if (first.useColors) {
13055         SendToProgram("white\n", &first); /*gnu kludge*/
13056       }
13057       SendTimeRemaining(&first, FALSE);
13058     }
13059     if (first.useColors) {
13060       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13061     }
13062     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13063     SetMachineThinkingEnables();
13064     first.maybeThinking = TRUE;
13065     StartClocks();
13066
13067     if (appData.autoFlipView && flipView) {
13068       flipView = !flipView;
13069       DrawPosition(FALSE, NULL);
13070       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13071     }
13072     if(bookHit) { // [HGM] book: simulate book reply
13073         static char bookMove[MSG_SIZ]; // a bit generous?
13074
13075         programStats.nodes = programStats.depth = programStats.time =
13076         programStats.score = programStats.got_only_move = 0;
13077         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13078
13079         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13080         strcat(bookMove, bookHit);
13081         HandleMachineMove(bookMove, &first);
13082     }
13083 }
13084
13085
13086 void
13087 DisplayTwoMachinesTitle()
13088 {
13089     char buf[MSG_SIZ];
13090     if (appData.matchGames > 0) {
13091         if(appData.tourneyFile[0]) {
13092           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
13093                    gameInfo.white, gameInfo.black,
13094                    nextGame+1, appData.matchGames+1,
13095                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13096         } else 
13097         if (first.twoMachinesColor[0] == 'w') {
13098           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13099                    gameInfo.white, gameInfo.black,
13100                    first.matchWins, second.matchWins,
13101                    matchGame - 1 - (first.matchWins + second.matchWins));
13102         } else {
13103           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
13104                    gameInfo.white, gameInfo.black,
13105                    second.matchWins, first.matchWins,
13106                    matchGame - 1 - (first.matchWins + second.matchWins));
13107         }
13108     } else {
13109       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
13110     }
13111     DisplayTitle(buf);
13112 }
13113
13114 void
13115 SettingsMenuIfReady()
13116 {
13117   if (second.lastPing != second.lastPong) {
13118     DisplayMessage("", _("Waiting for second chess program"));
13119     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13120     return;
13121   }
13122   ThawUI();
13123   DisplayMessage("", "");
13124   SettingsPopUp(&second);
13125 }
13126
13127 int
13128 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13129 {
13130     char buf[MSG_SIZ];
13131     if (cps->pr == NULL) {
13132         StartChessProgram(cps);
13133         if (cps->protocolVersion == 1) {
13134           retry();
13135         } else {
13136           /* kludge: allow timeout for initial "feature" command */
13137           FreezeUI();
13138           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13139           DisplayMessage("", buf);
13140           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13141         }
13142         return 1;
13143     }
13144     return 0;
13145 }
13146
13147 void
13148 TwoMachinesEvent P((void))
13149 {
13150     int i;
13151     char buf[MSG_SIZ];
13152     ChessProgramState *onmove;
13153     char *bookHit = NULL;
13154     static int stalling = 0;
13155     TimeMark now;
13156     long wait;
13157
13158     if (appData.noChessProgram) return;
13159
13160     switch (gameMode) {
13161       case TwoMachinesPlay:
13162         return;
13163       case MachinePlaysWhite:
13164       case MachinePlaysBlack:
13165         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13166             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13167             return;
13168         }
13169         /* fall through */
13170       case BeginningOfGame:
13171       case PlayFromGameFile:
13172       case EndOfGame:
13173         EditGameEvent();
13174         if (gameMode != EditGame) return;
13175         break;
13176       case EditPosition:
13177         EditPositionDone(TRUE);
13178         break;
13179       case AnalyzeMode:
13180       case AnalyzeFile:
13181         ExitAnalyzeMode();
13182         break;
13183       case EditGame:
13184       default:
13185         break;
13186     }
13187
13188 //    forwardMostMove = currentMove;
13189     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13190
13191     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13192
13193     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13194     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13195       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13196       return;
13197     }
13198     if(!stalling) {
13199       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13200       SendToProgram("force\n", &second);
13201       stalling = 1;
13202       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13203       return;
13204     }
13205     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13206     if(appData.matchPause>10000 || appData.matchPause<10)
13207                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13208     wait = SubtractTimeMarks(&now, &pauseStart);
13209     if(wait < appData.matchPause) {
13210         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13211         return;
13212     }
13213     stalling = 0;
13214     DisplayMessage("", "");
13215     if (startedFromSetupPosition) {
13216         SendBoard(&second, backwardMostMove);
13217     if (appData.debugMode) {
13218         fprintf(debugFP, "Two Machines\n");
13219     }
13220     }
13221     for (i = backwardMostMove; i < forwardMostMove; i++) {
13222         SendMoveToProgram(i, &second);
13223     }
13224
13225     gameMode = TwoMachinesPlay;
13226     pausing = FALSE;
13227     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13228     SetGameInfo();
13229     DisplayTwoMachinesTitle();
13230     firstMove = TRUE;
13231     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13232         onmove = &first;
13233     } else {
13234         onmove = &second;
13235     }
13236     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13237     SendToProgram(first.computerString, &first);
13238     if (first.sendName) {
13239       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13240       SendToProgram(buf, &first);
13241     }
13242     SendToProgram(second.computerString, &second);
13243     if (second.sendName) {
13244       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13245       SendToProgram(buf, &second);
13246     }
13247
13248     ResetClocks();
13249     if (!first.sendTime || !second.sendTime) {
13250         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13251         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13252     }
13253     if (onmove->sendTime) {
13254       if (onmove->useColors) {
13255         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13256       }
13257       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13258     }
13259     if (onmove->useColors) {
13260       SendToProgram(onmove->twoMachinesColor, onmove);
13261     }
13262     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13263 //    SendToProgram("go\n", onmove);
13264     onmove->maybeThinking = TRUE;
13265     SetMachineThinkingEnables();
13266
13267     StartClocks();
13268
13269     if(bookHit) { // [HGM] book: simulate book reply
13270         static char bookMove[MSG_SIZ]; // a bit generous?
13271
13272         programStats.nodes = programStats.depth = programStats.time =
13273         programStats.score = programStats.got_only_move = 0;
13274         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13275
13276         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13277         strcat(bookMove, bookHit);
13278         savedMessage = bookMove; // args for deferred call
13279         savedState = onmove;
13280         ScheduleDelayedEvent(DeferredBookMove, 1);
13281     }
13282 }
13283
13284 void
13285 TrainingEvent()
13286 {
13287     if (gameMode == Training) {
13288       SetTrainingModeOff();
13289       gameMode = PlayFromGameFile;
13290       DisplayMessage("", _("Training mode off"));
13291     } else {
13292       gameMode = Training;
13293       animateTraining = appData.animate;
13294
13295       /* make sure we are not already at the end of the game */
13296       if (currentMove < forwardMostMove) {
13297         SetTrainingModeOn();
13298         DisplayMessage("", _("Training mode on"));
13299       } else {
13300         gameMode = PlayFromGameFile;
13301         DisplayError(_("Already at end of game"), 0);
13302       }
13303     }
13304     ModeHighlight();
13305 }
13306
13307 void
13308 IcsClientEvent()
13309 {
13310     if (!appData.icsActive) return;
13311     switch (gameMode) {
13312       case IcsPlayingWhite:
13313       case IcsPlayingBlack:
13314       case IcsObserving:
13315       case IcsIdle:
13316       case BeginningOfGame:
13317       case IcsExamining:
13318         return;
13319
13320       case EditGame:
13321         break;
13322
13323       case EditPosition:
13324         EditPositionDone(TRUE);
13325         break;
13326
13327       case AnalyzeMode:
13328       case AnalyzeFile:
13329         ExitAnalyzeMode();
13330         break;
13331
13332       default:
13333         EditGameEvent();
13334         break;
13335     }
13336
13337     gameMode = IcsIdle;
13338     ModeHighlight();
13339     return;
13340 }
13341
13342
13343 void
13344 EditGameEvent()
13345 {
13346     int i;
13347
13348     switch (gameMode) {
13349       case Training:
13350         SetTrainingModeOff();
13351         break;
13352       case MachinePlaysWhite:
13353       case MachinePlaysBlack:
13354       case BeginningOfGame:
13355         SendToProgram("force\n", &first);
13356         SetUserThinkingEnables();
13357         break;
13358       case PlayFromGameFile:
13359         (void) StopLoadGameTimer();
13360         if (gameFileFP != NULL) {
13361             gameFileFP = NULL;
13362         }
13363         break;
13364       case EditPosition:
13365         EditPositionDone(TRUE);
13366         break;
13367       case AnalyzeMode:
13368       case AnalyzeFile:
13369         ExitAnalyzeMode();
13370         SendToProgram("force\n", &first);
13371         break;
13372       case TwoMachinesPlay:
13373         GameEnds(EndOfFile, NULL, GE_PLAYER);
13374         ResurrectChessProgram();
13375         SetUserThinkingEnables();
13376         break;
13377       case EndOfGame:
13378         ResurrectChessProgram();
13379         break;
13380       case IcsPlayingBlack:
13381       case IcsPlayingWhite:
13382         DisplayError(_("Warning: You are still playing a game"), 0);
13383         break;
13384       case IcsObserving:
13385         DisplayError(_("Warning: You are still observing a game"), 0);
13386         break;
13387       case IcsExamining:
13388         DisplayError(_("Warning: You are still examining a game"), 0);
13389         break;
13390       case IcsIdle:
13391         break;
13392       case EditGame:
13393       default:
13394         return;
13395     }
13396
13397     pausing = FALSE;
13398     StopClocks();
13399     first.offeredDraw = second.offeredDraw = 0;
13400
13401     if (gameMode == PlayFromGameFile) {
13402         whiteTimeRemaining = timeRemaining[0][currentMove];
13403         blackTimeRemaining = timeRemaining[1][currentMove];
13404         DisplayTitle("");
13405     }
13406
13407     if (gameMode == MachinePlaysWhite ||
13408         gameMode == MachinePlaysBlack ||
13409         gameMode == TwoMachinesPlay ||
13410         gameMode == EndOfGame) {
13411         i = forwardMostMove;
13412         while (i > currentMove) {
13413             SendToProgram("undo\n", &first);
13414             i--;
13415         }
13416         whiteTimeRemaining = timeRemaining[0][currentMove];
13417         blackTimeRemaining = timeRemaining[1][currentMove];
13418         DisplayBothClocks();
13419         if (whiteFlag || blackFlag) {
13420             whiteFlag = blackFlag = 0;
13421         }
13422         DisplayTitle("");
13423     }
13424
13425     gameMode = EditGame;
13426     ModeHighlight();
13427     SetGameInfo();
13428 }
13429
13430
13431 void
13432 EditPositionEvent()
13433 {
13434     if (gameMode == EditPosition) {
13435         EditGameEvent();
13436         return;
13437     }
13438
13439     EditGameEvent();
13440     if (gameMode != EditGame) return;
13441
13442     gameMode = EditPosition;
13443     ModeHighlight();
13444     SetGameInfo();
13445     if (currentMove > 0)
13446       CopyBoard(boards[0], boards[currentMove]);
13447
13448     blackPlaysFirst = !WhiteOnMove(currentMove);
13449     ResetClocks();
13450     currentMove = forwardMostMove = backwardMostMove = 0;
13451     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13452     DisplayMove(-1);
13453 }
13454
13455 void
13456 ExitAnalyzeMode()
13457 {
13458     /* [DM] icsEngineAnalyze - possible call from other functions */
13459     if (appData.icsEngineAnalyze) {
13460         appData.icsEngineAnalyze = FALSE;
13461
13462         DisplayMessage("",_("Close ICS engine analyze..."));
13463     }
13464     if (first.analysisSupport && first.analyzing) {
13465       SendToProgram("exit\n", &first);
13466       first.analyzing = FALSE;
13467     }
13468     thinkOutput[0] = NULLCHAR;
13469 }
13470
13471 void
13472 EditPositionDone(Boolean fakeRights)
13473 {
13474     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13475
13476     startedFromSetupPosition = TRUE;
13477     InitChessProgram(&first, FALSE);
13478     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13479       boards[0][EP_STATUS] = EP_NONE;
13480       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13481     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13482         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13483         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13484       } else boards[0][CASTLING][2] = NoRights;
13485     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13486         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13487         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13488       } else boards[0][CASTLING][5] = NoRights;
13489     }
13490     SendToProgram("force\n", &first);
13491     if (blackPlaysFirst) {
13492         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13493         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13494         currentMove = forwardMostMove = backwardMostMove = 1;
13495         CopyBoard(boards[1], boards[0]);
13496     } else {
13497         currentMove = forwardMostMove = backwardMostMove = 0;
13498     }
13499     SendBoard(&first, forwardMostMove);
13500     if (appData.debugMode) {
13501         fprintf(debugFP, "EditPosDone\n");
13502     }
13503     DisplayTitle("");
13504     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13505     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13506     gameMode = EditGame;
13507     ModeHighlight();
13508     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13509     ClearHighlights(); /* [AS] */
13510 }
13511
13512 /* Pause for `ms' milliseconds */
13513 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13514 void
13515 TimeDelay(ms)
13516      long ms;
13517 {
13518     TimeMark m1, m2;
13519
13520     GetTimeMark(&m1);
13521     do {
13522         GetTimeMark(&m2);
13523     } while (SubtractTimeMarks(&m2, &m1) < ms);
13524 }
13525
13526 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13527 void
13528 SendMultiLineToICS(buf)
13529      char *buf;
13530 {
13531     char temp[MSG_SIZ+1], *p;
13532     int len;
13533
13534     len = strlen(buf);
13535     if (len > MSG_SIZ)
13536       len = MSG_SIZ;
13537
13538     strncpy(temp, buf, len);
13539     temp[len] = 0;
13540
13541     p = temp;
13542     while (*p) {
13543         if (*p == '\n' || *p == '\r')
13544           *p = ' ';
13545         ++p;
13546     }
13547
13548     strcat(temp, "\n");
13549     SendToICS(temp);
13550     SendToPlayer(temp, strlen(temp));
13551 }
13552
13553 void
13554 SetWhiteToPlayEvent()
13555 {
13556     if (gameMode == EditPosition) {
13557         blackPlaysFirst = FALSE;
13558         DisplayBothClocks();    /* works because currentMove is 0 */
13559     } else if (gameMode == IcsExamining) {
13560         SendToICS(ics_prefix);
13561         SendToICS("tomove white\n");
13562     }
13563 }
13564
13565 void
13566 SetBlackToPlayEvent()
13567 {
13568     if (gameMode == EditPosition) {
13569         blackPlaysFirst = TRUE;
13570         currentMove = 1;        /* kludge */
13571         DisplayBothClocks();
13572         currentMove = 0;
13573     } else if (gameMode == IcsExamining) {
13574         SendToICS(ics_prefix);
13575         SendToICS("tomove black\n");
13576     }
13577 }
13578
13579 void
13580 EditPositionMenuEvent(selection, x, y)
13581      ChessSquare selection;
13582      int x, y;
13583 {
13584     char buf[MSG_SIZ];
13585     ChessSquare piece = boards[0][y][x];
13586
13587     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13588
13589     switch (selection) {
13590       case ClearBoard:
13591         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13592             SendToICS(ics_prefix);
13593             SendToICS("bsetup clear\n");
13594         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13595             SendToICS(ics_prefix);
13596             SendToICS("clearboard\n");
13597         } else {
13598             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13599                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13600                 for (y = 0; y < BOARD_HEIGHT; y++) {
13601                     if (gameMode == IcsExamining) {
13602                         if (boards[currentMove][y][x] != EmptySquare) {
13603                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13604                                     AAA + x, ONE + y);
13605                             SendToICS(buf);
13606                         }
13607                     } else {
13608                         boards[0][y][x] = p;
13609                     }
13610                 }
13611             }
13612         }
13613         if (gameMode == EditPosition) {
13614             DrawPosition(FALSE, boards[0]);
13615         }
13616         break;
13617
13618       case WhitePlay:
13619         SetWhiteToPlayEvent();
13620         break;
13621
13622       case BlackPlay:
13623         SetBlackToPlayEvent();
13624         break;
13625
13626       case EmptySquare:
13627         if (gameMode == IcsExamining) {
13628             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13629             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13630             SendToICS(buf);
13631         } else {
13632             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13633                 if(x == BOARD_LEFT-2) {
13634                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13635                     boards[0][y][1] = 0;
13636                 } else
13637                 if(x == BOARD_RGHT+1) {
13638                     if(y >= gameInfo.holdingsSize) break;
13639                     boards[0][y][BOARD_WIDTH-2] = 0;
13640                 } else break;
13641             }
13642             boards[0][y][x] = EmptySquare;
13643             DrawPosition(FALSE, boards[0]);
13644         }
13645         break;
13646
13647       case PromotePiece:
13648         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13649            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13650             selection = (ChessSquare) (PROMOTED piece);
13651         } else if(piece == EmptySquare) selection = WhiteSilver;
13652         else selection = (ChessSquare)((int)piece - 1);
13653         goto defaultlabel;
13654
13655       case DemotePiece:
13656         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13657            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13658             selection = (ChessSquare) (DEMOTED piece);
13659         } else if(piece == EmptySquare) selection = BlackSilver;
13660         else selection = (ChessSquare)((int)piece + 1);
13661         goto defaultlabel;
13662
13663       case WhiteQueen:
13664       case BlackQueen:
13665         if(gameInfo.variant == VariantShatranj ||
13666            gameInfo.variant == VariantXiangqi  ||
13667            gameInfo.variant == VariantCourier  ||
13668            gameInfo.variant == VariantMakruk     )
13669             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13670         goto defaultlabel;
13671
13672       case WhiteKing:
13673       case BlackKing:
13674         if(gameInfo.variant == VariantXiangqi)
13675             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13676         if(gameInfo.variant == VariantKnightmate)
13677             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13678       default:
13679         defaultlabel:
13680         if (gameMode == IcsExamining) {
13681             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13682             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13683                      PieceToChar(selection), AAA + x, ONE + y);
13684             SendToICS(buf);
13685         } else {
13686             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13687                 int n;
13688                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13689                     n = PieceToNumber(selection - BlackPawn);
13690                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13691                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13692                     boards[0][BOARD_HEIGHT-1-n][1]++;
13693                 } else
13694                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13695                     n = PieceToNumber(selection);
13696                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13697                     boards[0][n][BOARD_WIDTH-1] = selection;
13698                     boards[0][n][BOARD_WIDTH-2]++;
13699                 }
13700             } else
13701             boards[0][y][x] = selection;
13702             DrawPosition(TRUE, boards[0]);
13703         }
13704         break;
13705     }
13706 }
13707
13708
13709 void
13710 DropMenuEvent(selection, x, y)
13711      ChessSquare selection;
13712      int x, y;
13713 {
13714     ChessMove moveType;
13715
13716     switch (gameMode) {
13717       case IcsPlayingWhite:
13718       case MachinePlaysBlack:
13719         if (!WhiteOnMove(currentMove)) {
13720             DisplayMoveError(_("It is Black's turn"));
13721             return;
13722         }
13723         moveType = WhiteDrop;
13724         break;
13725       case IcsPlayingBlack:
13726       case MachinePlaysWhite:
13727         if (WhiteOnMove(currentMove)) {
13728             DisplayMoveError(_("It is White's turn"));
13729             return;
13730         }
13731         moveType = BlackDrop;
13732         break;
13733       case EditGame:
13734         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13735         break;
13736       default:
13737         return;
13738     }
13739
13740     if (moveType == BlackDrop && selection < BlackPawn) {
13741       selection = (ChessSquare) ((int) selection
13742                                  + (int) BlackPawn - (int) WhitePawn);
13743     }
13744     if (boards[currentMove][y][x] != EmptySquare) {
13745         DisplayMoveError(_("That square is occupied"));
13746         return;
13747     }
13748
13749     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13750 }
13751
13752 void
13753 AcceptEvent()
13754 {
13755     /* Accept a pending offer of any kind from opponent */
13756
13757     if (appData.icsActive) {
13758         SendToICS(ics_prefix);
13759         SendToICS("accept\n");
13760     } else if (cmailMsgLoaded) {
13761         if (currentMove == cmailOldMove &&
13762             commentList[cmailOldMove] != NULL &&
13763             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13764                    "Black offers a draw" : "White offers a draw")) {
13765             TruncateGame();
13766             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13767             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13768         } else {
13769             DisplayError(_("There is no pending offer on this move"), 0);
13770             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13771         }
13772     } else {
13773         /* Not used for offers from chess program */
13774     }
13775 }
13776
13777 void
13778 DeclineEvent()
13779 {
13780     /* Decline a pending offer of any kind from opponent */
13781
13782     if (appData.icsActive) {
13783         SendToICS(ics_prefix);
13784         SendToICS("decline\n");
13785     } else if (cmailMsgLoaded) {
13786         if (currentMove == cmailOldMove &&
13787             commentList[cmailOldMove] != NULL &&
13788             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13789                    "Black offers a draw" : "White offers a draw")) {
13790 #ifdef NOTDEF
13791             AppendComment(cmailOldMove, "Draw declined", TRUE);
13792             DisplayComment(cmailOldMove - 1, "Draw declined");
13793 #endif /*NOTDEF*/
13794         } else {
13795             DisplayError(_("There is no pending offer on this move"), 0);
13796         }
13797     } else {
13798         /* Not used for offers from chess program */
13799     }
13800 }
13801
13802 void
13803 RematchEvent()
13804 {
13805     /* Issue ICS rematch command */
13806     if (appData.icsActive) {
13807         SendToICS(ics_prefix);
13808         SendToICS("rematch\n");
13809     }
13810 }
13811
13812 void
13813 CallFlagEvent()
13814 {
13815     /* Call your opponent's flag (claim a win on time) */
13816     if (appData.icsActive) {
13817         SendToICS(ics_prefix);
13818         SendToICS("flag\n");
13819     } else {
13820         switch (gameMode) {
13821           default:
13822             return;
13823           case MachinePlaysWhite:
13824             if (whiteFlag) {
13825                 if (blackFlag)
13826                   GameEnds(GameIsDrawn, "Both players ran out of time",
13827                            GE_PLAYER);
13828                 else
13829                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13830             } else {
13831                 DisplayError(_("Your opponent is not out of time"), 0);
13832             }
13833             break;
13834           case MachinePlaysBlack:
13835             if (blackFlag) {
13836                 if (whiteFlag)
13837                   GameEnds(GameIsDrawn, "Both players ran out of time",
13838                            GE_PLAYER);
13839                 else
13840                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13841             } else {
13842                 DisplayError(_("Your opponent is not out of time"), 0);
13843             }
13844             break;
13845         }
13846     }
13847 }
13848
13849 void
13850 ClockClick(int which)
13851 {       // [HGM] code moved to back-end from winboard.c
13852         if(which) { // black clock
13853           if (gameMode == EditPosition || gameMode == IcsExamining) {
13854             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13855             SetBlackToPlayEvent();
13856           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
13857           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
13858           } else if (shiftKey) {
13859             AdjustClock(which, -1);
13860           } else if (gameMode == IcsPlayingWhite ||
13861                      gameMode == MachinePlaysBlack) {
13862             CallFlagEvent();
13863           }
13864         } else { // white clock
13865           if (gameMode == EditPosition || gameMode == IcsExamining) {
13866             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13867             SetWhiteToPlayEvent();
13868           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
13869           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
13870           } else if (shiftKey) {
13871             AdjustClock(which, -1);
13872           } else if (gameMode == IcsPlayingBlack ||
13873                    gameMode == MachinePlaysWhite) {
13874             CallFlagEvent();
13875           }
13876         }
13877 }
13878
13879 void
13880 DrawEvent()
13881 {
13882     /* Offer draw or accept pending draw offer from opponent */
13883
13884     if (appData.icsActive) {
13885         /* Note: tournament rules require draw offers to be
13886            made after you make your move but before you punch
13887            your clock.  Currently ICS doesn't let you do that;
13888            instead, you immediately punch your clock after making
13889            a move, but you can offer a draw at any time. */
13890
13891         SendToICS(ics_prefix);
13892         SendToICS("draw\n");
13893         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13894     } else if (cmailMsgLoaded) {
13895         if (currentMove == cmailOldMove &&
13896             commentList[cmailOldMove] != NULL &&
13897             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13898                    "Black offers a draw" : "White offers a draw")) {
13899             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13900             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13901         } else if (currentMove == cmailOldMove + 1) {
13902             char *offer = WhiteOnMove(cmailOldMove) ?
13903               "White offers a draw" : "Black offers a draw";
13904             AppendComment(currentMove, offer, TRUE);
13905             DisplayComment(currentMove - 1, offer);
13906             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13907         } else {
13908             DisplayError(_("You must make your move before offering a draw"), 0);
13909             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13910         }
13911     } else if (first.offeredDraw) {
13912         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13913     } else {
13914         if (first.sendDrawOffers) {
13915             SendToProgram("draw\n", &first);
13916             userOfferedDraw = TRUE;
13917         }
13918     }
13919 }
13920
13921 void
13922 AdjournEvent()
13923 {
13924     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13925
13926     if (appData.icsActive) {
13927         SendToICS(ics_prefix);
13928         SendToICS("adjourn\n");
13929     } else {
13930         /* Currently GNU Chess doesn't offer or accept Adjourns */
13931     }
13932 }
13933
13934
13935 void
13936 AbortEvent()
13937 {
13938     /* Offer Abort or accept pending Abort offer from opponent */
13939
13940     if (appData.icsActive) {
13941         SendToICS(ics_prefix);
13942         SendToICS("abort\n");
13943     } else {
13944         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13945     }
13946 }
13947
13948 void
13949 ResignEvent()
13950 {
13951     /* Resign.  You can do this even if it's not your turn. */
13952
13953     if (appData.icsActive) {
13954         SendToICS(ics_prefix);
13955         SendToICS("resign\n");
13956     } else {
13957         switch (gameMode) {
13958           case MachinePlaysWhite:
13959             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13960             break;
13961           case MachinePlaysBlack:
13962             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13963             break;
13964           case EditGame:
13965             if (cmailMsgLoaded) {
13966                 TruncateGame();
13967                 if (WhiteOnMove(cmailOldMove)) {
13968                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13969                 } else {
13970                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13971                 }
13972                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13973             }
13974             break;
13975           default:
13976             break;
13977         }
13978     }
13979 }
13980
13981
13982 void
13983 StopObservingEvent()
13984 {
13985     /* Stop observing current games */
13986     SendToICS(ics_prefix);
13987     SendToICS("unobserve\n");
13988 }
13989
13990 void
13991 StopExaminingEvent()
13992 {
13993     /* Stop observing current game */
13994     SendToICS(ics_prefix);
13995     SendToICS("unexamine\n");
13996 }
13997
13998 void
13999 ForwardInner(target)
14000      int target;
14001 {
14002     int limit;
14003
14004     if (appData.debugMode)
14005         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14006                 target, currentMove, forwardMostMove);
14007
14008     if (gameMode == EditPosition)
14009       return;
14010
14011     if (gameMode == PlayFromGameFile && !pausing)
14012       PauseEvent();
14013
14014     if (gameMode == IcsExamining && pausing)
14015       limit = pauseExamForwardMostMove;
14016     else
14017       limit = forwardMostMove;
14018
14019     if (target > limit) target = limit;
14020
14021     if (target > 0 && moveList[target - 1][0]) {
14022         int fromX, fromY, toX, toY;
14023         toX = moveList[target - 1][2] - AAA;
14024         toY = moveList[target - 1][3] - ONE;
14025         if (moveList[target - 1][1] == '@') {
14026             if (appData.highlightLastMove) {
14027                 SetHighlights(-1, -1, toX, toY);
14028             }
14029         } else {
14030             fromX = moveList[target - 1][0] - AAA;
14031             fromY = moveList[target - 1][1] - ONE;
14032             if (target == currentMove + 1) {
14033                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14034             }
14035             if (appData.highlightLastMove) {
14036                 SetHighlights(fromX, fromY, toX, toY);
14037             }
14038         }
14039     }
14040     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14041         gameMode == Training || gameMode == PlayFromGameFile ||
14042         gameMode == AnalyzeFile) {
14043         while (currentMove < target) {
14044             SendMoveToProgram(currentMove++, &first);
14045         }
14046     } else {
14047         currentMove = target;
14048     }
14049
14050     if (gameMode == EditGame || gameMode == EndOfGame) {
14051         whiteTimeRemaining = timeRemaining[0][currentMove];
14052         blackTimeRemaining = timeRemaining[1][currentMove];
14053     }
14054     DisplayBothClocks();
14055     DisplayMove(currentMove - 1);
14056     DrawPosition(FALSE, boards[currentMove]);
14057     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14058     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14059         DisplayComment(currentMove - 1, commentList[currentMove]);
14060     }
14061 }
14062
14063
14064 void
14065 ForwardEvent()
14066 {
14067     if (gameMode == IcsExamining && !pausing) {
14068         SendToICS(ics_prefix);
14069         SendToICS("forward\n");
14070     } else {
14071         ForwardInner(currentMove + 1);
14072     }
14073 }
14074
14075 void
14076 ToEndEvent()
14077 {
14078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14079         /* to optimze, we temporarily turn off analysis mode while we feed
14080          * the remaining moves to the engine. Otherwise we get analysis output
14081          * after each move.
14082          */
14083         if (first.analysisSupport) {
14084           SendToProgram("exit\nforce\n", &first);
14085           first.analyzing = FALSE;
14086         }
14087     }
14088
14089     if (gameMode == IcsExamining && !pausing) {
14090         SendToICS(ics_prefix);
14091         SendToICS("forward 999999\n");
14092     } else {
14093         ForwardInner(forwardMostMove);
14094     }
14095
14096     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14097         /* we have fed all the moves, so reactivate analysis mode */
14098         SendToProgram("analyze\n", &first);
14099         first.analyzing = TRUE;
14100         /*first.maybeThinking = TRUE;*/
14101         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14102     }
14103 }
14104
14105 void
14106 BackwardInner(target)
14107      int target;
14108 {
14109     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14110
14111     if (appData.debugMode)
14112         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14113                 target, currentMove, forwardMostMove);
14114
14115     if (gameMode == EditPosition) return;
14116     if (currentMove <= backwardMostMove) {
14117         ClearHighlights();
14118         DrawPosition(full_redraw, boards[currentMove]);
14119         return;
14120     }
14121     if (gameMode == PlayFromGameFile && !pausing)
14122       PauseEvent();
14123
14124     if (moveList[target][0]) {
14125         int fromX, fromY, toX, toY;
14126         toX = moveList[target][2] - AAA;
14127         toY = moveList[target][3] - ONE;
14128         if (moveList[target][1] == '@') {
14129             if (appData.highlightLastMove) {
14130                 SetHighlights(-1, -1, toX, toY);
14131             }
14132         } else {
14133             fromX = moveList[target][0] - AAA;
14134             fromY = moveList[target][1] - ONE;
14135             if (target == currentMove - 1) {
14136                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14137             }
14138             if (appData.highlightLastMove) {
14139                 SetHighlights(fromX, fromY, toX, toY);
14140             }
14141         }
14142     }
14143     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14144         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14145         while (currentMove > target) {
14146             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14147                 // null move cannot be undone. Reload program with move history before it.
14148                 int i;
14149                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14150                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14151                 }
14152                 SendBoard(&first, i); 
14153                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14154                 break;
14155             }
14156             SendToProgram("undo\n", &first);
14157             currentMove--;
14158         }
14159     } else {
14160         currentMove = target;
14161     }
14162
14163     if (gameMode == EditGame || gameMode == EndOfGame) {
14164         whiteTimeRemaining = timeRemaining[0][currentMove];
14165         blackTimeRemaining = timeRemaining[1][currentMove];
14166     }
14167     DisplayBothClocks();
14168     DisplayMove(currentMove - 1);
14169     DrawPosition(full_redraw, boards[currentMove]);
14170     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14171     // [HGM] PV info: routine tests if comment empty
14172     DisplayComment(currentMove - 1, commentList[currentMove]);
14173 }
14174
14175 void
14176 BackwardEvent()
14177 {
14178     if (gameMode == IcsExamining && !pausing) {
14179         SendToICS(ics_prefix);
14180         SendToICS("backward\n");
14181     } else {
14182         BackwardInner(currentMove - 1);
14183     }
14184 }
14185
14186 void
14187 ToStartEvent()
14188 {
14189     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14190         /* to optimize, we temporarily turn off analysis mode while we undo
14191          * all the moves. Otherwise we get analysis output after each undo.
14192          */
14193         if (first.analysisSupport) {
14194           SendToProgram("exit\nforce\n", &first);
14195           first.analyzing = FALSE;
14196         }
14197     }
14198
14199     if (gameMode == IcsExamining && !pausing) {
14200         SendToICS(ics_prefix);
14201         SendToICS("backward 999999\n");
14202     } else {
14203         BackwardInner(backwardMostMove);
14204     }
14205
14206     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14207         /* we have fed all the moves, so reactivate analysis mode */
14208         SendToProgram("analyze\n", &first);
14209         first.analyzing = TRUE;
14210         /*first.maybeThinking = TRUE;*/
14211         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14212     }
14213 }
14214
14215 void
14216 ToNrEvent(int to)
14217 {
14218   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14219   if (to >= forwardMostMove) to = forwardMostMove;
14220   if (to <= backwardMostMove) to = backwardMostMove;
14221   if (to < currentMove) {
14222     BackwardInner(to);
14223   } else {
14224     ForwardInner(to);
14225   }
14226 }
14227
14228 void
14229 RevertEvent(Boolean annotate)
14230 {
14231     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14232         return;
14233     }
14234     if (gameMode != IcsExamining) {
14235         DisplayError(_("You are not examining a game"), 0);
14236         return;
14237     }
14238     if (pausing) {
14239         DisplayError(_("You can't revert while pausing"), 0);
14240         return;
14241     }
14242     SendToICS(ics_prefix);
14243     SendToICS("revert\n");
14244 }
14245
14246 void
14247 RetractMoveEvent()
14248 {
14249     switch (gameMode) {
14250       case MachinePlaysWhite:
14251       case MachinePlaysBlack:
14252         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14253             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14254             return;
14255         }
14256         if (forwardMostMove < 2) return;
14257         currentMove = forwardMostMove = forwardMostMove - 2;
14258         whiteTimeRemaining = timeRemaining[0][currentMove];
14259         blackTimeRemaining = timeRemaining[1][currentMove];
14260         DisplayBothClocks();
14261         DisplayMove(currentMove - 1);
14262         ClearHighlights();/*!! could figure this out*/
14263         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14264         SendToProgram("remove\n", &first);
14265         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14266         break;
14267
14268       case BeginningOfGame:
14269       default:
14270         break;
14271
14272       case IcsPlayingWhite:
14273       case IcsPlayingBlack:
14274         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14275             SendToICS(ics_prefix);
14276             SendToICS("takeback 2\n");
14277         } else {
14278             SendToICS(ics_prefix);
14279             SendToICS("takeback 1\n");
14280         }
14281         break;
14282     }
14283 }
14284
14285 void
14286 MoveNowEvent()
14287 {
14288     ChessProgramState *cps;
14289
14290     switch (gameMode) {
14291       case MachinePlaysWhite:
14292         if (!WhiteOnMove(forwardMostMove)) {
14293             DisplayError(_("It is your turn"), 0);
14294             return;
14295         }
14296         cps = &first;
14297         break;
14298       case MachinePlaysBlack:
14299         if (WhiteOnMove(forwardMostMove)) {
14300             DisplayError(_("It is your turn"), 0);
14301             return;
14302         }
14303         cps = &first;
14304         break;
14305       case TwoMachinesPlay:
14306         if (WhiteOnMove(forwardMostMove) ==
14307             (first.twoMachinesColor[0] == 'w')) {
14308             cps = &first;
14309         } else {
14310             cps = &second;
14311         }
14312         break;
14313       case BeginningOfGame:
14314       default:
14315         return;
14316     }
14317     SendToProgram("?\n", cps);
14318 }
14319
14320 void
14321 TruncateGameEvent()
14322 {
14323     EditGameEvent();
14324     if (gameMode != EditGame) return;
14325     TruncateGame();
14326 }
14327
14328 void
14329 TruncateGame()
14330 {
14331     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14332     if (forwardMostMove > currentMove) {
14333         if (gameInfo.resultDetails != NULL) {
14334             free(gameInfo.resultDetails);
14335             gameInfo.resultDetails = NULL;
14336             gameInfo.result = GameUnfinished;
14337         }
14338         forwardMostMove = currentMove;
14339         HistorySet(parseList, backwardMostMove, forwardMostMove,
14340                    currentMove-1);
14341     }
14342 }
14343
14344 void
14345 HintEvent()
14346 {
14347     if (appData.noChessProgram) return;
14348     switch (gameMode) {
14349       case MachinePlaysWhite:
14350         if (WhiteOnMove(forwardMostMove)) {
14351             DisplayError(_("Wait until your turn"), 0);
14352             return;
14353         }
14354         break;
14355       case BeginningOfGame:
14356       case MachinePlaysBlack:
14357         if (!WhiteOnMove(forwardMostMove)) {
14358             DisplayError(_("Wait until your turn"), 0);
14359             return;
14360         }
14361         break;
14362       default:
14363         DisplayError(_("No hint available"), 0);
14364         return;
14365     }
14366     SendToProgram("hint\n", &first);
14367     hintRequested = TRUE;
14368 }
14369
14370 void
14371 BookEvent()
14372 {
14373     if (appData.noChessProgram) return;
14374     switch (gameMode) {
14375       case MachinePlaysWhite:
14376         if (WhiteOnMove(forwardMostMove)) {
14377             DisplayError(_("Wait until your turn"), 0);
14378             return;
14379         }
14380         break;
14381       case BeginningOfGame:
14382       case MachinePlaysBlack:
14383         if (!WhiteOnMove(forwardMostMove)) {
14384             DisplayError(_("Wait until your turn"), 0);
14385             return;
14386         }
14387         break;
14388       case EditPosition:
14389         EditPositionDone(TRUE);
14390         break;
14391       case TwoMachinesPlay:
14392         return;
14393       default:
14394         break;
14395     }
14396     SendToProgram("bk\n", &first);
14397     bookOutput[0] = NULLCHAR;
14398     bookRequested = TRUE;
14399 }
14400
14401 void
14402 AboutGameEvent()
14403 {
14404     char *tags = PGNTags(&gameInfo);
14405     TagsPopUp(tags, CmailMsg());
14406     free(tags);
14407 }
14408
14409 /* end button procedures */
14410
14411 void
14412 PrintPosition(fp, move)
14413      FILE *fp;
14414      int move;
14415 {
14416     int i, j;
14417
14418     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14419         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14420             char c = PieceToChar(boards[move][i][j]);
14421             fputc(c == 'x' ? '.' : c, fp);
14422             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14423         }
14424     }
14425     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14426       fprintf(fp, "white to play\n");
14427     else
14428       fprintf(fp, "black to play\n");
14429 }
14430
14431 void
14432 PrintOpponents(fp)
14433      FILE *fp;
14434 {
14435     if (gameInfo.white != NULL) {
14436         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14437     } else {
14438         fprintf(fp, "\n");
14439     }
14440 }
14441
14442 /* Find last component of program's own name, using some heuristics */
14443 void
14444 TidyProgramName(prog, host, buf)
14445      char *prog, *host, buf[MSG_SIZ];
14446 {
14447     char *p, *q;
14448     int local = (strcmp(host, "localhost") == 0);
14449     while (!local && (p = strchr(prog, ';')) != NULL) {
14450         p++;
14451         while (*p == ' ') p++;
14452         prog = p;
14453     }
14454     if (*prog == '"' || *prog == '\'') {
14455         q = strchr(prog + 1, *prog);
14456     } else {
14457         q = strchr(prog, ' ');
14458     }
14459     if (q == NULL) q = prog + strlen(prog);
14460     p = q;
14461     while (p >= prog && *p != '/' && *p != '\\') p--;
14462     p++;
14463     if(p == prog && *p == '"') p++;
14464     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14465     memcpy(buf, p, q - p);
14466     buf[q - p] = NULLCHAR;
14467     if (!local) {
14468         strcat(buf, "@");
14469         strcat(buf, host);
14470     }
14471 }
14472
14473 char *
14474 TimeControlTagValue()
14475 {
14476     char buf[MSG_SIZ];
14477     if (!appData.clockMode) {
14478       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14479     } else if (movesPerSession > 0) {
14480       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14481     } else if (timeIncrement == 0) {
14482       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14483     } else {
14484       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14485     }
14486     return StrSave(buf);
14487 }
14488
14489 void
14490 SetGameInfo()
14491 {
14492     /* This routine is used only for certain modes */
14493     VariantClass v = gameInfo.variant;
14494     ChessMove r = GameUnfinished;
14495     char *p = NULL;
14496
14497     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14498         r = gameInfo.result;
14499         p = gameInfo.resultDetails;
14500         gameInfo.resultDetails = NULL;
14501     }
14502     ClearGameInfo(&gameInfo);
14503     gameInfo.variant = v;
14504
14505     switch (gameMode) {
14506       case MachinePlaysWhite:
14507         gameInfo.event = StrSave( appData.pgnEventHeader );
14508         gameInfo.site = StrSave(HostName());
14509         gameInfo.date = PGNDate();
14510         gameInfo.round = StrSave("-");
14511         gameInfo.white = StrSave(first.tidy);
14512         gameInfo.black = StrSave(UserName());
14513         gameInfo.timeControl = TimeControlTagValue();
14514         break;
14515
14516       case MachinePlaysBlack:
14517         gameInfo.event = StrSave( appData.pgnEventHeader );
14518         gameInfo.site = StrSave(HostName());
14519         gameInfo.date = PGNDate();
14520         gameInfo.round = StrSave("-");
14521         gameInfo.white = StrSave(UserName());
14522         gameInfo.black = StrSave(first.tidy);
14523         gameInfo.timeControl = TimeControlTagValue();
14524         break;
14525
14526       case TwoMachinesPlay:
14527         gameInfo.event = StrSave( appData.pgnEventHeader );
14528         gameInfo.site = StrSave(HostName());
14529         gameInfo.date = PGNDate();
14530         if (roundNr > 0) {
14531             char buf[MSG_SIZ];
14532             snprintf(buf, MSG_SIZ, "%d", roundNr);
14533             gameInfo.round = StrSave(buf);
14534         } else {
14535             gameInfo.round = StrSave("-");
14536         }
14537         if (first.twoMachinesColor[0] == 'w') {
14538             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14539             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14540         } else {
14541             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14542             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14543         }
14544         gameInfo.timeControl = TimeControlTagValue();
14545         break;
14546
14547       case EditGame:
14548         gameInfo.event = StrSave("Edited game");
14549         gameInfo.site = StrSave(HostName());
14550         gameInfo.date = PGNDate();
14551         gameInfo.round = StrSave("-");
14552         gameInfo.white = StrSave("-");
14553         gameInfo.black = StrSave("-");
14554         gameInfo.result = r;
14555         gameInfo.resultDetails = p;
14556         break;
14557
14558       case EditPosition:
14559         gameInfo.event = StrSave("Edited position");
14560         gameInfo.site = StrSave(HostName());
14561         gameInfo.date = PGNDate();
14562         gameInfo.round = StrSave("-");
14563         gameInfo.white = StrSave("-");
14564         gameInfo.black = StrSave("-");
14565         break;
14566
14567       case IcsPlayingWhite:
14568       case IcsPlayingBlack:
14569       case IcsObserving:
14570       case IcsExamining:
14571         break;
14572
14573       case PlayFromGameFile:
14574         gameInfo.event = StrSave("Game from non-PGN file");
14575         gameInfo.site = StrSave(HostName());
14576         gameInfo.date = PGNDate();
14577         gameInfo.round = StrSave("-");
14578         gameInfo.white = StrSave("?");
14579         gameInfo.black = StrSave("?");
14580         break;
14581
14582       default:
14583         break;
14584     }
14585 }
14586
14587 void
14588 ReplaceComment(index, text)
14589      int index;
14590      char *text;
14591 {
14592     int len;
14593     char *p;
14594     float score;
14595
14596     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14597        pvInfoList[index-1].depth == len &&
14598        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14599        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14600     while (*text == '\n') text++;
14601     len = strlen(text);
14602     while (len > 0 && text[len - 1] == '\n') len--;
14603
14604     if (commentList[index] != NULL)
14605       free(commentList[index]);
14606
14607     if (len == 0) {
14608         commentList[index] = NULL;
14609         return;
14610     }
14611   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14612       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14613       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14614     commentList[index] = (char *) malloc(len + 2);
14615     strncpy(commentList[index], text, len);
14616     commentList[index][len] = '\n';
14617     commentList[index][len + 1] = NULLCHAR;
14618   } else {
14619     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14620     char *p;
14621     commentList[index] = (char *) malloc(len + 7);
14622     safeStrCpy(commentList[index], "{\n", 3);
14623     safeStrCpy(commentList[index]+2, text, len+1);
14624     commentList[index][len+2] = NULLCHAR;
14625     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14626     strcat(commentList[index], "\n}\n");
14627   }
14628 }
14629
14630 void
14631 CrushCRs(text)
14632      char *text;
14633 {
14634   char *p = text;
14635   char *q = text;
14636   char ch;
14637
14638   do {
14639     ch = *p++;
14640     if (ch == '\r') continue;
14641     *q++ = ch;
14642   } while (ch != '\0');
14643 }
14644
14645 void
14646 AppendComment(index, text, addBraces)
14647      int index;
14648      char *text;
14649      Boolean addBraces; // [HGM] braces: tells if we should add {}
14650 {
14651     int oldlen, len;
14652     char *old;
14653
14654 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14655     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14656
14657     CrushCRs(text);
14658     while (*text == '\n') text++;
14659     len = strlen(text);
14660     while (len > 0 && text[len - 1] == '\n') len--;
14661
14662     if (len == 0) return;
14663
14664     if (commentList[index] != NULL) {
14665       Boolean addClosingBrace = addBraces;
14666         old = commentList[index];
14667         oldlen = strlen(old);
14668         while(commentList[index][oldlen-1] ==  '\n')
14669           commentList[index][--oldlen] = NULLCHAR;
14670         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14671         safeStrCpy(commentList[index], old, oldlen + len + 6);
14672         free(old);
14673         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14674         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14675           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14676           while (*text == '\n') { text++; len--; }
14677           commentList[index][--oldlen] = NULLCHAR;
14678       }
14679         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14680         else          strcat(commentList[index], "\n");
14681         strcat(commentList[index], text);
14682         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14683         else          strcat(commentList[index], "\n");
14684     } else {
14685         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14686         if(addBraces)
14687           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14688         else commentList[index][0] = NULLCHAR;
14689         strcat(commentList[index], text);
14690         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14691         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14692     }
14693 }
14694
14695 static char * FindStr( char * text, char * sub_text )
14696 {
14697     char * result = strstr( text, sub_text );
14698
14699     if( result != NULL ) {
14700         result += strlen( sub_text );
14701     }
14702
14703     return result;
14704 }
14705
14706 /* [AS] Try to extract PV info from PGN comment */
14707 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14708 char *GetInfoFromComment( int index, char * text )
14709 {
14710     char * sep = text, *p;
14711
14712     if( text != NULL && index > 0 ) {
14713         int score = 0;
14714         int depth = 0;
14715         int time = -1, sec = 0, deci;
14716         char * s_eval = FindStr( text, "[%eval " );
14717         char * s_emt = FindStr( text, "[%emt " );
14718
14719         if( s_eval != NULL || s_emt != NULL ) {
14720             /* New style */
14721             char delim;
14722
14723             if( s_eval != NULL ) {
14724                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14725                     return text;
14726                 }
14727
14728                 if( delim != ']' ) {
14729                     return text;
14730                 }
14731             }
14732
14733             if( s_emt != NULL ) {
14734             }
14735                 return text;
14736         }
14737         else {
14738             /* We expect something like: [+|-]nnn.nn/dd */
14739             int score_lo = 0;
14740
14741             if(*text != '{') return text; // [HGM] braces: must be normal comment
14742
14743             sep = strchr( text, '/' );
14744             if( sep == NULL || sep < (text+4) ) {
14745                 return text;
14746             }
14747
14748             p = text;
14749             if(p[1] == '(') { // comment starts with PV
14750                p = strchr(p, ')'); // locate end of PV
14751                if(p == NULL || sep < p+5) return text;
14752                // at this point we have something like "{(.*) +0.23/6 ..."
14753                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14754                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14755                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14756             }
14757             time = -1; sec = -1; deci = -1;
14758             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14759                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14760                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14761                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14762                 return text;
14763             }
14764
14765             if( score_lo < 0 || score_lo >= 100 ) {
14766                 return text;
14767             }
14768
14769             if(sec >= 0) time = 600*time + 10*sec; else
14770             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14771
14772             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14773
14774             /* [HGM] PV time: now locate end of PV info */
14775             while( *++sep >= '0' && *sep <= '9'); // strip depth
14776             if(time >= 0)
14777             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14778             if(sec >= 0)
14779             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14780             if(deci >= 0)
14781             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14782             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14783         }
14784
14785         if( depth <= 0 ) {
14786             return text;
14787         }
14788
14789         if( time < 0 ) {
14790             time = -1;
14791         }
14792
14793         pvInfoList[index-1].depth = depth;
14794         pvInfoList[index-1].score = score;
14795         pvInfoList[index-1].time  = 10*time; // centi-sec
14796         if(*sep == '}') *sep = 0; else *--sep = '{';
14797         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14798     }
14799     return sep;
14800 }
14801
14802 void
14803 SendToProgram(message, cps)
14804      char *message;
14805      ChessProgramState *cps;
14806 {
14807     int count, outCount, error;
14808     char buf[MSG_SIZ];
14809
14810     if (cps->pr == NULL) return;
14811     Attention(cps);
14812
14813     if (appData.debugMode) {
14814         TimeMark now;
14815         GetTimeMark(&now);
14816         fprintf(debugFP, "%ld >%-6s: %s",
14817                 SubtractTimeMarks(&now, &programStartTime),
14818                 cps->which, message);
14819     }
14820
14821     count = strlen(message);
14822     outCount = OutputToProcess(cps->pr, message, count, &error);
14823     if (outCount < count && !exiting
14824                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14825       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14826       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14827         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14828             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14829                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14830                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14831                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14832             } else {
14833                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14834                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14835                 gameInfo.result = res;
14836             }
14837             gameInfo.resultDetails = StrSave(buf);
14838         }
14839         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14840         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14841     }
14842 }
14843
14844 void
14845 ReceiveFromProgram(isr, closure, message, count, error)
14846      InputSourceRef isr;
14847      VOIDSTAR closure;
14848      char *message;
14849      int count;
14850      int error;
14851 {
14852     char *end_str;
14853     char buf[MSG_SIZ];
14854     ChessProgramState *cps = (ChessProgramState *)closure;
14855
14856     if (isr != cps->isr) return; /* Killed intentionally */
14857     if (count <= 0) {
14858         if (count == 0) {
14859             RemoveInputSource(cps->isr);
14860             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14861             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14862                     _(cps->which), cps->program);
14863         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14864                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14865                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14866                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14867                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14868                 } else {
14869                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14870                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14871                     gameInfo.result = res;
14872                 }
14873                 gameInfo.resultDetails = StrSave(buf);
14874             }
14875             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14876             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14877         } else {
14878             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14879                     _(cps->which), cps->program);
14880             RemoveInputSource(cps->isr);
14881
14882             /* [AS] Program is misbehaving badly... kill it */
14883             if( count == -2 ) {
14884                 DestroyChildProcess( cps->pr, 9 );
14885                 cps->pr = NoProc;
14886             }
14887
14888             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14889         }
14890         return;
14891     }
14892
14893     if ((end_str = strchr(message, '\r')) != NULL)
14894       *end_str = NULLCHAR;
14895     if ((end_str = strchr(message, '\n')) != NULL)
14896       *end_str = NULLCHAR;
14897
14898     if (appData.debugMode) {
14899         TimeMark now; int print = 1;
14900         char *quote = ""; char c; int i;
14901
14902         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14903                 char start = message[0];
14904                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14905                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14906                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14907                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14908                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14909                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14910                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14911                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14912                    sscanf(message, "hint: %c", &c)!=1 && 
14913                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14914                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14915                     print = (appData.engineComments >= 2);
14916                 }
14917                 message[0] = start; // restore original message
14918         }
14919         if(print) {
14920                 GetTimeMark(&now);
14921                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14922                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14923                         quote,
14924                         message);
14925         }
14926     }
14927
14928     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14929     if (appData.icsEngineAnalyze) {
14930         if (strstr(message, "whisper") != NULL ||
14931              strstr(message, "kibitz") != NULL ||
14932             strstr(message, "tellics") != NULL) return;
14933     }
14934
14935     HandleMachineMove(message, cps);
14936 }
14937
14938
14939 void
14940 SendTimeControl(cps, mps, tc, inc, sd, st)
14941      ChessProgramState *cps;
14942      int mps, inc, sd, st;
14943      long tc;
14944 {
14945     char buf[MSG_SIZ];
14946     int seconds;
14947
14948     if( timeControl_2 > 0 ) {
14949         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14950             tc = timeControl_2;
14951         }
14952     }
14953     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14954     inc /= cps->timeOdds;
14955     st  /= cps->timeOdds;
14956
14957     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14958
14959     if (st > 0) {
14960       /* Set exact time per move, normally using st command */
14961       if (cps->stKludge) {
14962         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14963         seconds = st % 60;
14964         if (seconds == 0) {
14965           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14966         } else {
14967           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14968         }
14969       } else {
14970         snprintf(buf, MSG_SIZ, "st %d\n", st);
14971       }
14972     } else {
14973       /* Set conventional or incremental time control, using level command */
14974       if (seconds == 0) {
14975         /* Note old gnuchess bug -- minutes:seconds used to not work.
14976            Fixed in later versions, but still avoid :seconds
14977            when seconds is 0. */
14978         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14979       } else {
14980         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14981                  seconds, inc/1000.);
14982       }
14983     }
14984     SendToProgram(buf, cps);
14985
14986     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14987     /* Orthogonally, limit search to given depth */
14988     if (sd > 0) {
14989       if (cps->sdKludge) {
14990         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14991       } else {
14992         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14993       }
14994       SendToProgram(buf, cps);
14995     }
14996
14997     if(cps->nps >= 0) { /* [HGM] nps */
14998         if(cps->supportsNPS == FALSE)
14999           cps->nps = -1; // don't use if engine explicitly says not supported!
15000         else {
15001           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15002           SendToProgram(buf, cps);
15003         }
15004     }
15005 }
15006
15007 ChessProgramState *WhitePlayer()
15008 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15009 {
15010     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15011        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15012         return &second;
15013     return &first;
15014 }
15015
15016 void
15017 SendTimeRemaining(cps, machineWhite)
15018      ChessProgramState *cps;
15019      int /*boolean*/ machineWhite;
15020 {
15021     char message[MSG_SIZ];
15022     long time, otime;
15023
15024     /* Note: this routine must be called when the clocks are stopped
15025        or when they have *just* been set or switched; otherwise
15026        it will be off by the time since the current tick started.
15027     */
15028     if (machineWhite) {
15029         time = whiteTimeRemaining / 10;
15030         otime = blackTimeRemaining / 10;
15031     } else {
15032         time = blackTimeRemaining / 10;
15033         otime = whiteTimeRemaining / 10;
15034     }
15035     /* [HGM] translate opponent's time by time-odds factor */
15036     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15037     if (appData.debugMode) {
15038         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15039     }
15040
15041     if (time <= 0) time = 1;
15042     if (otime <= 0) otime = 1;
15043
15044     snprintf(message, MSG_SIZ, "time %ld\n", time);
15045     SendToProgram(message, cps);
15046
15047     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15048     SendToProgram(message, cps);
15049 }
15050
15051 int
15052 BoolFeature(p, name, loc, cps)
15053      char **p;
15054      char *name;
15055      int *loc;
15056      ChessProgramState *cps;
15057 {
15058   char buf[MSG_SIZ];
15059   int len = strlen(name);
15060   int val;
15061
15062   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15063     (*p) += len + 1;
15064     sscanf(*p, "%d", &val);
15065     *loc = (val != 0);
15066     while (**p && **p != ' ')
15067       (*p)++;
15068     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15069     SendToProgram(buf, cps);
15070     return TRUE;
15071   }
15072   return FALSE;
15073 }
15074
15075 int
15076 IntFeature(p, name, loc, cps)
15077      char **p;
15078      char *name;
15079      int *loc;
15080      ChessProgramState *cps;
15081 {
15082   char buf[MSG_SIZ];
15083   int len = strlen(name);
15084   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15085     (*p) += len + 1;
15086     sscanf(*p, "%d", loc);
15087     while (**p && **p != ' ') (*p)++;
15088     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15089     SendToProgram(buf, cps);
15090     return TRUE;
15091   }
15092   return FALSE;
15093 }
15094
15095 int
15096 StringFeature(p, name, loc, cps)
15097      char **p;
15098      char *name;
15099      char loc[];
15100      ChessProgramState *cps;
15101 {
15102   char buf[MSG_SIZ];
15103   int len = strlen(name);
15104   if (strncmp((*p), name, len) == 0
15105       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15106     (*p) += len + 2;
15107     sscanf(*p, "%[^\"]", loc);
15108     while (**p && **p != '\"') (*p)++;
15109     if (**p == '\"') (*p)++;
15110     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15111     SendToProgram(buf, cps);
15112     return TRUE;
15113   }
15114   return FALSE;
15115 }
15116
15117 int
15118 ParseOption(Option *opt, ChessProgramState *cps)
15119 // [HGM] options: process the string that defines an engine option, and determine
15120 // name, type, default value, and allowed value range
15121 {
15122         char *p, *q, buf[MSG_SIZ];
15123         int n, min = (-1)<<31, max = 1<<31, def;
15124
15125         if(p = strstr(opt->name, " -spin ")) {
15126             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15127             if(max < min) max = min; // enforce consistency
15128             if(def < min) def = min;
15129             if(def > max) def = max;
15130             opt->value = def;
15131             opt->min = min;
15132             opt->max = max;
15133             opt->type = Spin;
15134         } else if((p = strstr(opt->name, " -slider "))) {
15135             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15136             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15137             if(max < min) max = min; // enforce consistency
15138             if(def < min) def = min;
15139             if(def > max) def = max;
15140             opt->value = def;
15141             opt->min = min;
15142             opt->max = max;
15143             opt->type = Spin; // Slider;
15144         } else if((p = strstr(opt->name, " -string "))) {
15145             opt->textValue = p+9;
15146             opt->type = TextBox;
15147         } else if((p = strstr(opt->name, " -file "))) {
15148             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15149             opt->textValue = p+7;
15150             opt->type = FileName; // FileName;
15151         } else if((p = strstr(opt->name, " -path "))) {
15152             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15153             opt->textValue = p+7;
15154             opt->type = PathName; // PathName;
15155         } else if(p = strstr(opt->name, " -check ")) {
15156             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15157             opt->value = (def != 0);
15158             opt->type = CheckBox;
15159         } else if(p = strstr(opt->name, " -combo ")) {
15160             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15161             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15162             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15163             opt->value = n = 0;
15164             while(q = StrStr(q, " /// ")) {
15165                 n++; *q = 0;    // count choices, and null-terminate each of them
15166                 q += 5;
15167                 if(*q == '*') { // remember default, which is marked with * prefix
15168                     q++;
15169                     opt->value = n;
15170                 }
15171                 cps->comboList[cps->comboCnt++] = q;
15172             }
15173             cps->comboList[cps->comboCnt++] = NULL;
15174             opt->max = n + 1;
15175             opt->type = ComboBox;
15176         } else if(p = strstr(opt->name, " -button")) {
15177             opt->type = Button;
15178         } else if(p = strstr(opt->name, " -save")) {
15179             opt->type = SaveButton;
15180         } else return FALSE;
15181         *p = 0; // terminate option name
15182         // now look if the command-line options define a setting for this engine option.
15183         if(cps->optionSettings && cps->optionSettings[0])
15184             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15185         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15186           snprintf(buf, MSG_SIZ, "option %s", p);
15187                 if(p = strstr(buf, ",")) *p = 0;
15188                 if(q = strchr(buf, '=')) switch(opt->type) {
15189                     case ComboBox:
15190                         for(n=0; n<opt->max; n++)
15191                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15192                         break;
15193                     case TextBox:
15194                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15195                         break;
15196                     case Spin:
15197                     case CheckBox:
15198                         opt->value = atoi(q+1);
15199                     default:
15200                         break;
15201                 }
15202                 strcat(buf, "\n");
15203                 SendToProgram(buf, cps);
15204         }
15205         return TRUE;
15206 }
15207
15208 void
15209 FeatureDone(cps, val)
15210      ChessProgramState* cps;
15211      int val;
15212 {
15213   DelayedEventCallback cb = GetDelayedEvent();
15214   if ((cb == InitBackEnd3 && cps == &first) ||
15215       (cb == SettingsMenuIfReady && cps == &second) ||
15216       (cb == LoadEngine) ||
15217       (cb == TwoMachinesEventIfReady)) {
15218     CancelDelayedEvent();
15219     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15220   }
15221   cps->initDone = val;
15222 }
15223
15224 /* Parse feature command from engine */
15225 void
15226 ParseFeatures(args, cps)
15227      char* args;
15228      ChessProgramState *cps;
15229 {
15230   char *p = args;
15231   char *q;
15232   int val;
15233   char buf[MSG_SIZ];
15234
15235   for (;;) {
15236     while (*p == ' ') p++;
15237     if (*p == NULLCHAR) return;
15238
15239     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15240     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15241     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15242     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15243     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15244     if (BoolFeature(&p, "reuse", &val, cps)) {
15245       /* Engine can disable reuse, but can't enable it if user said no */
15246       if (!val) cps->reuse = FALSE;
15247       continue;
15248     }
15249     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15250     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15251       if (gameMode == TwoMachinesPlay) {
15252         DisplayTwoMachinesTitle();
15253       } else {
15254         DisplayTitle("");
15255       }
15256       continue;
15257     }
15258     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15259     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15260     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15261     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15262     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15263     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15264     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15265     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15266     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15267     if (IntFeature(&p, "done", &val, cps)) {
15268       FeatureDone(cps, val);
15269       continue;
15270     }
15271     /* Added by Tord: */
15272     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15273     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15274     /* End of additions by Tord */
15275
15276     /* [HGM] added features: */
15277     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15278     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15279     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15280     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15281     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15282     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15283     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15284         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15285           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15286             SendToProgram(buf, cps);
15287             continue;
15288         }
15289         if(cps->nrOptions >= MAX_OPTIONS) {
15290             cps->nrOptions--;
15291             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15292             DisplayError(buf, 0);
15293         }
15294         continue;
15295     }
15296     /* End of additions by HGM */
15297
15298     /* unknown feature: complain and skip */
15299     q = p;
15300     while (*q && *q != '=') q++;
15301     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15302     SendToProgram(buf, cps);
15303     p = q;
15304     if (*p == '=') {
15305       p++;
15306       if (*p == '\"') {
15307         p++;
15308         while (*p && *p != '\"') p++;
15309         if (*p == '\"') p++;
15310       } else {
15311         while (*p && *p != ' ') p++;
15312       }
15313     }
15314   }
15315
15316 }
15317
15318 void
15319 PeriodicUpdatesEvent(newState)
15320      int newState;
15321 {
15322     if (newState == appData.periodicUpdates)
15323       return;
15324
15325     appData.periodicUpdates=newState;
15326
15327     /* Display type changes, so update it now */
15328 //    DisplayAnalysis();
15329
15330     /* Get the ball rolling again... */
15331     if (newState) {
15332         AnalysisPeriodicEvent(1);
15333         StartAnalysisClock();
15334     }
15335 }
15336
15337 void
15338 PonderNextMoveEvent(newState)
15339      int newState;
15340 {
15341     if (newState == appData.ponderNextMove) return;
15342     if (gameMode == EditPosition) EditPositionDone(TRUE);
15343     if (newState) {
15344         SendToProgram("hard\n", &first);
15345         if (gameMode == TwoMachinesPlay) {
15346             SendToProgram("hard\n", &second);
15347         }
15348     } else {
15349         SendToProgram("easy\n", &first);
15350         thinkOutput[0] = NULLCHAR;
15351         if (gameMode == TwoMachinesPlay) {
15352             SendToProgram("easy\n", &second);
15353         }
15354     }
15355     appData.ponderNextMove = newState;
15356 }
15357
15358 void
15359 NewSettingEvent(option, feature, command, value)
15360      char *command;
15361      int option, value, *feature;
15362 {
15363     char buf[MSG_SIZ];
15364
15365     if (gameMode == EditPosition) EditPositionDone(TRUE);
15366     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15367     if(feature == NULL || *feature) SendToProgram(buf, &first);
15368     if (gameMode == TwoMachinesPlay) {
15369         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15370     }
15371 }
15372
15373 void
15374 ShowThinkingEvent()
15375 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15376 {
15377     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15378     int newState = appData.showThinking
15379         // [HGM] thinking: other features now need thinking output as well
15380         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15381
15382     if (oldState == newState) return;
15383     oldState = newState;
15384     if (gameMode == EditPosition) EditPositionDone(TRUE);
15385     if (oldState) {
15386         SendToProgram("post\n", &first);
15387         if (gameMode == TwoMachinesPlay) {
15388             SendToProgram("post\n", &second);
15389         }
15390     } else {
15391         SendToProgram("nopost\n", &first);
15392         thinkOutput[0] = NULLCHAR;
15393         if (gameMode == TwoMachinesPlay) {
15394             SendToProgram("nopost\n", &second);
15395         }
15396     }
15397 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15398 }
15399
15400 void
15401 AskQuestionEvent(title, question, replyPrefix, which)
15402      char *title; char *question; char *replyPrefix; char *which;
15403 {
15404   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15405   if (pr == NoProc) return;
15406   AskQuestion(title, question, replyPrefix, pr);
15407 }
15408
15409 void
15410 TypeInEvent(char firstChar)
15411 {
15412     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15413         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15414         gameMode == AnalyzeMode || gameMode == EditGame || 
15415         gameMode == EditPosition || gameMode == IcsExamining ||
15416         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15417         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15418                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15419                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15420         gameMode == Training) PopUpMoveDialog(firstChar);
15421 }
15422
15423 void
15424 TypeInDoneEvent(char *move)
15425 {
15426         Board board;
15427         int n, fromX, fromY, toX, toY;
15428         char promoChar;
15429         ChessMove moveType;
15430
15431         // [HGM] FENedit
15432         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15433                 EditPositionPasteFEN(move);
15434                 return;
15435         }
15436         // [HGM] movenum: allow move number to be typed in any mode
15437         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15438           ToNrEvent(2*n-1);
15439           return;
15440         }
15441
15442       if (gameMode != EditGame && currentMove != forwardMostMove && 
15443         gameMode != Training) {
15444         DisplayMoveError(_("Displayed move is not current"));
15445       } else {
15446         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15447           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15448         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15449         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15450           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15451           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15452         } else {
15453           DisplayMoveError(_("Could not parse move"));
15454         }
15455       }
15456 }
15457
15458 void
15459 DisplayMove(moveNumber)
15460      int moveNumber;
15461 {
15462     char message[MSG_SIZ];
15463     char res[MSG_SIZ];
15464     char cpThinkOutput[MSG_SIZ];
15465
15466     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15467
15468     if (moveNumber == forwardMostMove - 1 ||
15469         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15470
15471         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15472
15473         if (strchr(cpThinkOutput, '\n')) {
15474             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15475         }
15476     } else {
15477         *cpThinkOutput = NULLCHAR;
15478     }
15479
15480     /* [AS] Hide thinking from human user */
15481     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15482         *cpThinkOutput = NULLCHAR;
15483         if( thinkOutput[0] != NULLCHAR ) {
15484             int i;
15485
15486             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15487                 cpThinkOutput[i] = '.';
15488             }
15489             cpThinkOutput[i] = NULLCHAR;
15490             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15491         }
15492     }
15493
15494     if (moveNumber == forwardMostMove - 1 &&
15495         gameInfo.resultDetails != NULL) {
15496         if (gameInfo.resultDetails[0] == NULLCHAR) {
15497           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15498         } else {
15499           snprintf(res, MSG_SIZ, " {%s} %s",
15500                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15501         }
15502     } else {
15503         res[0] = NULLCHAR;
15504     }
15505
15506     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15507         DisplayMessage(res, cpThinkOutput);
15508     } else {
15509       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15510                 WhiteOnMove(moveNumber) ? " " : ".. ",
15511                 parseList[moveNumber], res);
15512         DisplayMessage(message, cpThinkOutput);
15513     }
15514 }
15515
15516 void
15517 DisplayComment(moveNumber, text)
15518      int moveNumber;
15519      char *text;
15520 {
15521     char title[MSG_SIZ];
15522
15523     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15524       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15525     } else {
15526       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15527               WhiteOnMove(moveNumber) ? " " : ".. ",
15528               parseList[moveNumber]);
15529     }
15530     if (text != NULL && (appData.autoDisplayComment || commentUp))
15531         CommentPopUp(title, text);
15532 }
15533
15534 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15535  * might be busy thinking or pondering.  It can be omitted if your
15536  * gnuchess is configured to stop thinking immediately on any user
15537  * input.  However, that gnuchess feature depends on the FIONREAD
15538  * ioctl, which does not work properly on some flavors of Unix.
15539  */
15540 void
15541 Attention(cps)
15542      ChessProgramState *cps;
15543 {
15544 #if ATTENTION
15545     if (!cps->useSigint) return;
15546     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15547     switch (gameMode) {
15548       case MachinePlaysWhite:
15549       case MachinePlaysBlack:
15550       case TwoMachinesPlay:
15551       case IcsPlayingWhite:
15552       case IcsPlayingBlack:
15553       case AnalyzeMode:
15554       case AnalyzeFile:
15555         /* Skip if we know it isn't thinking */
15556         if (!cps->maybeThinking) return;
15557         if (appData.debugMode)
15558           fprintf(debugFP, "Interrupting %s\n", cps->which);
15559         InterruptChildProcess(cps->pr);
15560         cps->maybeThinking = FALSE;
15561         break;
15562       default:
15563         break;
15564     }
15565 #endif /*ATTENTION*/
15566 }
15567
15568 int
15569 CheckFlags()
15570 {
15571     if (whiteTimeRemaining <= 0) {
15572         if (!whiteFlag) {
15573             whiteFlag = TRUE;
15574             if (appData.icsActive) {
15575                 if (appData.autoCallFlag &&
15576                     gameMode == IcsPlayingBlack && !blackFlag) {
15577                   SendToICS(ics_prefix);
15578                   SendToICS("flag\n");
15579                 }
15580             } else {
15581                 if (blackFlag) {
15582                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15583                 } else {
15584                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15585                     if (appData.autoCallFlag) {
15586                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15587                         return TRUE;
15588                     }
15589                 }
15590             }
15591         }
15592     }
15593     if (blackTimeRemaining <= 0) {
15594         if (!blackFlag) {
15595             blackFlag = TRUE;
15596             if (appData.icsActive) {
15597                 if (appData.autoCallFlag &&
15598                     gameMode == IcsPlayingWhite && !whiteFlag) {
15599                   SendToICS(ics_prefix);
15600                   SendToICS("flag\n");
15601                 }
15602             } else {
15603                 if (whiteFlag) {
15604                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15605                 } else {
15606                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15607                     if (appData.autoCallFlag) {
15608                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15609                         return TRUE;
15610                     }
15611                 }
15612             }
15613         }
15614     }
15615     return FALSE;
15616 }
15617
15618 void
15619 CheckTimeControl()
15620 {
15621     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15622         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15623
15624     /*
15625      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15626      */
15627     if ( !WhiteOnMove(forwardMostMove) ) {
15628         /* White made time control */
15629         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15630         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15631         /* [HGM] time odds: correct new time quota for time odds! */
15632                                             / WhitePlayer()->timeOdds;
15633         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15634     } else {
15635         lastBlack -= blackTimeRemaining;
15636         /* Black made time control */
15637         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15638                                             / WhitePlayer()->other->timeOdds;
15639         lastWhite = whiteTimeRemaining;
15640     }
15641 }
15642
15643 void
15644 DisplayBothClocks()
15645 {
15646     int wom = gameMode == EditPosition ?
15647       !blackPlaysFirst : WhiteOnMove(currentMove);
15648     DisplayWhiteClock(whiteTimeRemaining, wom);
15649     DisplayBlackClock(blackTimeRemaining, !wom);
15650 }
15651
15652
15653 /* Timekeeping seems to be a portability nightmare.  I think everyone
15654    has ftime(), but I'm really not sure, so I'm including some ifdefs
15655    to use other calls if you don't.  Clocks will be less accurate if
15656    you have neither ftime nor gettimeofday.
15657 */
15658
15659 /* VS 2008 requires the #include outside of the function */
15660 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15661 #include <sys/timeb.h>
15662 #endif
15663
15664 /* Get the current time as a TimeMark */
15665 void
15666 GetTimeMark(tm)
15667      TimeMark *tm;
15668 {
15669 #if HAVE_GETTIMEOFDAY
15670
15671     struct timeval timeVal;
15672     struct timezone timeZone;
15673
15674     gettimeofday(&timeVal, &timeZone);
15675     tm->sec = (long) timeVal.tv_sec;
15676     tm->ms = (int) (timeVal.tv_usec / 1000L);
15677
15678 #else /*!HAVE_GETTIMEOFDAY*/
15679 #if HAVE_FTIME
15680
15681 // include <sys/timeb.h> / moved to just above start of function
15682     struct timeb timeB;
15683
15684     ftime(&timeB);
15685     tm->sec = (long) timeB.time;
15686     tm->ms = (int) timeB.millitm;
15687
15688 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15689     tm->sec = (long) time(NULL);
15690     tm->ms = 0;
15691 #endif
15692 #endif
15693 }
15694
15695 /* Return the difference in milliseconds between two
15696    time marks.  We assume the difference will fit in a long!
15697 */
15698 long
15699 SubtractTimeMarks(tm2, tm1)
15700      TimeMark *tm2, *tm1;
15701 {
15702     return 1000L*(tm2->sec - tm1->sec) +
15703            (long) (tm2->ms - tm1->ms);
15704 }
15705
15706
15707 /*
15708  * Code to manage the game clocks.
15709  *
15710  * In tournament play, black starts the clock and then white makes a move.
15711  * We give the human user a slight advantage if he is playing white---the
15712  * clocks don't run until he makes his first move, so it takes zero time.
15713  * Also, we don't account for network lag, so we could get out of sync
15714  * with GNU Chess's clock -- but then, referees are always right.
15715  */
15716
15717 static TimeMark tickStartTM;
15718 static long intendedTickLength;
15719
15720 long
15721 NextTickLength(timeRemaining)
15722      long timeRemaining;
15723 {
15724     long nominalTickLength, nextTickLength;
15725
15726     if (timeRemaining > 0L && timeRemaining <= 10000L)
15727       nominalTickLength = 100L;
15728     else
15729       nominalTickLength = 1000L;
15730     nextTickLength = timeRemaining % nominalTickLength;
15731     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15732
15733     return nextTickLength;
15734 }
15735
15736 /* Adjust clock one minute up or down */
15737 void
15738 AdjustClock(Boolean which, int dir)
15739 {
15740     if(which) blackTimeRemaining += 60000*dir;
15741     else      whiteTimeRemaining += 60000*dir;
15742     DisplayBothClocks();
15743 }
15744
15745 /* Stop clocks and reset to a fresh time control */
15746 void
15747 ResetClocks()
15748 {
15749     (void) StopClockTimer();
15750     if (appData.icsActive) {
15751         whiteTimeRemaining = blackTimeRemaining = 0;
15752     } else if (searchTime) {
15753         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15754         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15755     } else { /* [HGM] correct new time quote for time odds */
15756         whiteTC = blackTC = fullTimeControlString;
15757         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15758         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15759     }
15760     if (whiteFlag || blackFlag) {
15761         DisplayTitle("");
15762         whiteFlag = blackFlag = FALSE;
15763     }
15764     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15765     DisplayBothClocks();
15766 }
15767
15768 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15769
15770 /* Decrement running clock by amount of time that has passed */
15771 void
15772 DecrementClocks()
15773 {
15774     long timeRemaining;
15775     long lastTickLength, fudge;
15776     TimeMark now;
15777
15778     if (!appData.clockMode) return;
15779     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15780
15781     GetTimeMark(&now);
15782
15783     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15784
15785     /* Fudge if we woke up a little too soon */
15786     fudge = intendedTickLength - lastTickLength;
15787     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15788
15789     if (WhiteOnMove(forwardMostMove)) {
15790         if(whiteNPS >= 0) lastTickLength = 0;
15791         timeRemaining = whiteTimeRemaining -= lastTickLength;
15792         if(timeRemaining < 0 && !appData.icsActive) {
15793             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15794             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15795                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15796                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15797             }
15798         }
15799         DisplayWhiteClock(whiteTimeRemaining - fudge,
15800                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15801     } else {
15802         if(blackNPS >= 0) lastTickLength = 0;
15803         timeRemaining = blackTimeRemaining -= lastTickLength;
15804         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15805             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15806             if(suddenDeath) {
15807                 blackStartMove = forwardMostMove;
15808                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15809             }
15810         }
15811         DisplayBlackClock(blackTimeRemaining - fudge,
15812                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15813     }
15814     if (CheckFlags()) return;
15815
15816     tickStartTM = now;
15817     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15818     StartClockTimer(intendedTickLength);
15819
15820     /* if the time remaining has fallen below the alarm threshold, sound the
15821      * alarm. if the alarm has sounded and (due to a takeback or time control
15822      * with increment) the time remaining has increased to a level above the
15823      * threshold, reset the alarm so it can sound again.
15824      */
15825
15826     if (appData.icsActive && appData.icsAlarm) {
15827
15828         /* make sure we are dealing with the user's clock */
15829         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15830                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15831            )) return;
15832
15833         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15834             alarmSounded = FALSE;
15835         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15836             PlayAlarmSound();
15837             alarmSounded = TRUE;
15838         }
15839     }
15840 }
15841
15842
15843 /* A player has just moved, so stop the previously running
15844    clock and (if in clock mode) start the other one.
15845    We redisplay both clocks in case we're in ICS mode, because
15846    ICS gives us an update to both clocks after every move.
15847    Note that this routine is called *after* forwardMostMove
15848    is updated, so the last fractional tick must be subtracted
15849    from the color that is *not* on move now.
15850 */
15851 void
15852 SwitchClocks(int newMoveNr)
15853 {
15854     long lastTickLength;
15855     TimeMark now;
15856     int flagged = FALSE;
15857
15858     GetTimeMark(&now);
15859
15860     if (StopClockTimer() && appData.clockMode) {
15861         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15862         if (!WhiteOnMove(forwardMostMove)) {
15863             if(blackNPS >= 0) lastTickLength = 0;
15864             blackTimeRemaining -= lastTickLength;
15865            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15866 //         if(pvInfoList[forwardMostMove].time == -1)
15867                  pvInfoList[forwardMostMove].time =               // use GUI time
15868                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15869         } else {
15870            if(whiteNPS >= 0) lastTickLength = 0;
15871            whiteTimeRemaining -= lastTickLength;
15872            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15873 //         if(pvInfoList[forwardMostMove].time == -1)
15874                  pvInfoList[forwardMostMove].time =
15875                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15876         }
15877         flagged = CheckFlags();
15878     }
15879     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15880     CheckTimeControl();
15881
15882     if (flagged || !appData.clockMode) return;
15883
15884     switch (gameMode) {
15885       case MachinePlaysBlack:
15886       case MachinePlaysWhite:
15887       case BeginningOfGame:
15888         if (pausing) return;
15889         break;
15890
15891       case EditGame:
15892       case PlayFromGameFile:
15893       case IcsExamining:
15894         return;
15895
15896       default:
15897         break;
15898     }
15899
15900     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15901         if(WhiteOnMove(forwardMostMove))
15902              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15903         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15904     }
15905
15906     tickStartTM = now;
15907     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15908       whiteTimeRemaining : blackTimeRemaining);
15909     StartClockTimer(intendedTickLength);
15910 }
15911
15912
15913 /* Stop both clocks */
15914 void
15915 StopClocks()
15916 {
15917     long lastTickLength;
15918     TimeMark now;
15919
15920     if (!StopClockTimer()) return;
15921     if (!appData.clockMode) return;
15922
15923     GetTimeMark(&now);
15924
15925     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15926     if (WhiteOnMove(forwardMostMove)) {
15927         if(whiteNPS >= 0) lastTickLength = 0;
15928         whiteTimeRemaining -= lastTickLength;
15929         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15930     } else {
15931         if(blackNPS >= 0) lastTickLength = 0;
15932         blackTimeRemaining -= lastTickLength;
15933         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15934     }
15935     CheckFlags();
15936 }
15937
15938 /* Start clock of player on move.  Time may have been reset, so
15939    if clock is already running, stop and restart it. */
15940 void
15941 StartClocks()
15942 {
15943     (void) StopClockTimer(); /* in case it was running already */
15944     DisplayBothClocks();
15945     if (CheckFlags()) return;
15946
15947     if (!appData.clockMode) return;
15948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15949
15950     GetTimeMark(&tickStartTM);
15951     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15952       whiteTimeRemaining : blackTimeRemaining);
15953
15954    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15955     whiteNPS = blackNPS = -1;
15956     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15957        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15958         whiteNPS = first.nps;
15959     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15960        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15961         blackNPS = first.nps;
15962     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15963         whiteNPS = second.nps;
15964     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15965         blackNPS = second.nps;
15966     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15967
15968     StartClockTimer(intendedTickLength);
15969 }
15970
15971 char *
15972 TimeString(ms)
15973      long ms;
15974 {
15975     long second, minute, hour, day;
15976     char *sign = "";
15977     static char buf[32];
15978
15979     if (ms > 0 && ms <= 9900) {
15980       /* convert milliseconds to tenths, rounding up */
15981       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15982
15983       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15984       return buf;
15985     }
15986
15987     /* convert milliseconds to seconds, rounding up */
15988     /* use floating point to avoid strangeness of integer division
15989        with negative dividends on many machines */
15990     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15991
15992     if (second < 0) {
15993         sign = "-";
15994         second = -second;
15995     }
15996
15997     day = second / (60 * 60 * 24);
15998     second = second % (60 * 60 * 24);
15999     hour = second / (60 * 60);
16000     second = second % (60 * 60);
16001     minute = second / 60;
16002     second = second % 60;
16003
16004     if (day > 0)
16005       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16006               sign, day, hour, minute, second);
16007     else if (hour > 0)
16008       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16009     else
16010       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16011
16012     return buf;
16013 }
16014
16015
16016 /*
16017  * This is necessary because some C libraries aren't ANSI C compliant yet.
16018  */
16019 char *
16020 StrStr(string, match)
16021      char *string, *match;
16022 {
16023     int i, length;
16024
16025     length = strlen(match);
16026
16027     for (i = strlen(string) - length; i >= 0; i--, string++)
16028       if (!strncmp(match, string, length))
16029         return string;
16030
16031     return NULL;
16032 }
16033
16034 char *
16035 StrCaseStr(string, match)
16036      char *string, *match;
16037 {
16038     int i, j, length;
16039
16040     length = strlen(match);
16041
16042     for (i = strlen(string) - length; i >= 0; i--, string++) {
16043         for (j = 0; j < length; j++) {
16044             if (ToLower(match[j]) != ToLower(string[j]))
16045               break;
16046         }
16047         if (j == length) return string;
16048     }
16049
16050     return NULL;
16051 }
16052
16053 #ifndef _amigados
16054 int
16055 StrCaseCmp(s1, s2)
16056      char *s1, *s2;
16057 {
16058     char c1, c2;
16059
16060     for (;;) {
16061         c1 = ToLower(*s1++);
16062         c2 = ToLower(*s2++);
16063         if (c1 > c2) return 1;
16064         if (c1 < c2) return -1;
16065         if (c1 == NULLCHAR) return 0;
16066     }
16067 }
16068
16069
16070 int
16071 ToLower(c)
16072      int c;
16073 {
16074     return isupper(c) ? tolower(c) : c;
16075 }
16076
16077
16078 int
16079 ToUpper(c)
16080      int c;
16081 {
16082     return islower(c) ? toupper(c) : c;
16083 }
16084 #endif /* !_amigados    */
16085
16086 char *
16087 StrSave(s)
16088      char *s;
16089 {
16090   char *ret;
16091
16092   if ((ret = (char *) malloc(strlen(s) + 1)))
16093     {
16094       safeStrCpy(ret, s, strlen(s)+1);
16095     }
16096   return ret;
16097 }
16098
16099 char *
16100 StrSavePtr(s, savePtr)
16101      char *s, **savePtr;
16102 {
16103     if (*savePtr) {
16104         free(*savePtr);
16105     }
16106     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16107       safeStrCpy(*savePtr, s, strlen(s)+1);
16108     }
16109     return(*savePtr);
16110 }
16111
16112 char *
16113 PGNDate()
16114 {
16115     time_t clock;
16116     struct tm *tm;
16117     char buf[MSG_SIZ];
16118
16119     clock = time((time_t *)NULL);
16120     tm = localtime(&clock);
16121     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16122             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16123     return StrSave(buf);
16124 }
16125
16126
16127 char *
16128 PositionToFEN(move, overrideCastling)
16129      int move;
16130      char *overrideCastling;
16131 {
16132     int i, j, fromX, fromY, toX, toY;
16133     int whiteToPlay;
16134     char buf[MSG_SIZ];
16135     char *p, *q;
16136     int emptycount;
16137     ChessSquare piece;
16138
16139     whiteToPlay = (gameMode == EditPosition) ?
16140       !blackPlaysFirst : (move % 2 == 0);
16141     p = buf;
16142
16143     /* Piece placement data */
16144     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16145         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16146         emptycount = 0;
16147         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16148             if (boards[move][i][j] == EmptySquare) {
16149                 emptycount++;
16150             } else { ChessSquare piece = boards[move][i][j];
16151                 if (emptycount > 0) {
16152                     if(emptycount<10) /* [HGM] can be >= 10 */
16153                         *p++ = '0' + emptycount;
16154                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16155                     emptycount = 0;
16156                 }
16157                 if(PieceToChar(piece) == '+') {
16158                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16159                     *p++ = '+';
16160                     piece = (ChessSquare)(DEMOTED piece);
16161                 }
16162                 *p++ = PieceToChar(piece);
16163                 if(p[-1] == '~') {
16164                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16165                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16166                     *p++ = '~';
16167                 }
16168             }
16169         }
16170         if (emptycount > 0) {
16171             if(emptycount<10) /* [HGM] can be >= 10 */
16172                 *p++ = '0' + emptycount;
16173             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16174             emptycount = 0;
16175         }
16176         *p++ = '/';
16177     }
16178     *(p - 1) = ' ';
16179
16180     /* [HGM] print Crazyhouse or Shogi holdings */
16181     if( gameInfo.holdingsWidth ) {
16182         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16183         q = p;
16184         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16185             piece = boards[move][i][BOARD_WIDTH-1];
16186             if( piece != EmptySquare )
16187               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16188                   *p++ = PieceToChar(piece);
16189         }
16190         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16191             piece = boards[move][BOARD_HEIGHT-i-1][0];
16192             if( piece != EmptySquare )
16193               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16194                   *p++ = PieceToChar(piece);
16195         }
16196
16197         if( q == p ) *p++ = '-';
16198         *p++ = ']';
16199         *p++ = ' ';
16200     }
16201
16202     /* Active color */
16203     *p++ = whiteToPlay ? 'w' : 'b';
16204     *p++ = ' ';
16205
16206   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16207     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16208   } else {
16209   if(nrCastlingRights) {
16210      q = p;
16211      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16212        /* [HGM] write directly from rights */
16213            if(boards[move][CASTLING][2] != NoRights &&
16214               boards[move][CASTLING][0] != NoRights   )
16215                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16216            if(boards[move][CASTLING][2] != NoRights &&
16217               boards[move][CASTLING][1] != NoRights   )
16218                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16219            if(boards[move][CASTLING][5] != NoRights &&
16220               boards[move][CASTLING][3] != NoRights   )
16221                 *p++ = boards[move][CASTLING][3] + AAA;
16222            if(boards[move][CASTLING][5] != NoRights &&
16223               boards[move][CASTLING][4] != NoRights   )
16224                 *p++ = boards[move][CASTLING][4] + AAA;
16225      } else {
16226
16227         /* [HGM] write true castling rights */
16228         if( nrCastlingRights == 6 ) {
16229             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16230                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16231             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16232                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16233             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16234                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16235             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16236                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16237         }
16238      }
16239      if (q == p) *p++ = '-'; /* No castling rights */
16240      *p++ = ' ';
16241   }
16242
16243   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16244      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16245     /* En passant target square */
16246     if (move > backwardMostMove) {
16247         fromX = moveList[move - 1][0] - AAA;
16248         fromY = moveList[move - 1][1] - ONE;
16249         toX = moveList[move - 1][2] - AAA;
16250         toY = moveList[move - 1][3] - ONE;
16251         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16252             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16253             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16254             fromX == toX) {
16255             /* 2-square pawn move just happened */
16256             *p++ = toX + AAA;
16257             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16258         } else {
16259             *p++ = '-';
16260         }
16261     } else if(move == backwardMostMove) {
16262         // [HGM] perhaps we should always do it like this, and forget the above?
16263         if((signed char)boards[move][EP_STATUS] >= 0) {
16264             *p++ = boards[move][EP_STATUS] + AAA;
16265             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16266         } else {
16267             *p++ = '-';
16268         }
16269     } else {
16270         *p++ = '-';
16271     }
16272     *p++ = ' ';
16273   }
16274   }
16275
16276     /* [HGM] find reversible plies */
16277     {   int i = 0, j=move;
16278
16279         if (appData.debugMode) { int k;
16280             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16281             for(k=backwardMostMove; k<=forwardMostMove; k++)
16282                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16283
16284         }
16285
16286         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16287         if( j == backwardMostMove ) i += initialRulePlies;
16288         sprintf(p, "%d ", i);
16289         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16290     }
16291     /* Fullmove number */
16292     sprintf(p, "%d", (move / 2) + 1);
16293
16294     return StrSave(buf);
16295 }
16296
16297 Boolean
16298 ParseFEN(board, blackPlaysFirst, fen)
16299     Board board;
16300      int *blackPlaysFirst;
16301      char *fen;
16302 {
16303     int i, j;
16304     char *p, c;
16305     int emptycount;
16306     ChessSquare piece;
16307
16308     p = fen;
16309
16310     /* [HGM] by default clear Crazyhouse holdings, if present */
16311     if(gameInfo.holdingsWidth) {
16312        for(i=0; i<BOARD_HEIGHT; i++) {
16313            board[i][0]             = EmptySquare; /* black holdings */
16314            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16315            board[i][1]             = (ChessSquare) 0; /* black counts */
16316            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16317        }
16318     }
16319
16320     /* Piece placement data */
16321     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16322         j = 0;
16323         for (;;) {
16324             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16325                 if (*p == '/') p++;
16326                 emptycount = gameInfo.boardWidth - j;
16327                 while (emptycount--)
16328                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16329                 break;
16330 #if(BOARD_FILES >= 10)
16331             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16332                 p++; emptycount=10;
16333                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16334                 while (emptycount--)
16335                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16336 #endif
16337             } else if (isdigit(*p)) {
16338                 emptycount = *p++ - '0';
16339                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16340                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16341                 while (emptycount--)
16342                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16343             } else if (*p == '+' || isalpha(*p)) {
16344                 if (j >= gameInfo.boardWidth) return FALSE;
16345                 if(*p=='+') {
16346                     piece = CharToPiece(*++p);
16347                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16348                     piece = (ChessSquare) (PROMOTED piece ); p++;
16349                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16350                 } else piece = CharToPiece(*p++);
16351
16352                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16353                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16354                     piece = (ChessSquare) (PROMOTED piece);
16355                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16356                     p++;
16357                 }
16358                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16359             } else {
16360                 return FALSE;
16361             }
16362         }
16363     }
16364     while (*p == '/' || *p == ' ') p++;
16365
16366     /* [HGM] look for Crazyhouse holdings here */
16367     while(*p==' ') p++;
16368     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16369         if(*p == '[') p++;
16370         if(*p == '-' ) p++; /* empty holdings */ else {
16371             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16372             /* if we would allow FEN reading to set board size, we would   */
16373             /* have to add holdings and shift the board read so far here   */
16374             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16375                 p++;
16376                 if((int) piece >= (int) BlackPawn ) {
16377                     i = (int)piece - (int)BlackPawn;
16378                     i = PieceToNumber((ChessSquare)i);
16379                     if( i >= gameInfo.holdingsSize ) return FALSE;
16380                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16381                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16382                 } else {
16383                     i = (int)piece - (int)WhitePawn;
16384                     i = PieceToNumber((ChessSquare)i);
16385                     if( i >= gameInfo.holdingsSize ) return FALSE;
16386                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16387                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16388                 }
16389             }
16390         }
16391         if(*p == ']') p++;
16392     }
16393
16394     while(*p == ' ') p++;
16395
16396     /* Active color */
16397     c = *p++;
16398     if(appData.colorNickNames) {
16399       if( c == appData.colorNickNames[0] ) c = 'w'; else
16400       if( c == appData.colorNickNames[1] ) c = 'b';
16401     }
16402     switch (c) {
16403       case 'w':
16404         *blackPlaysFirst = FALSE;
16405         break;
16406       case 'b':
16407         *blackPlaysFirst = TRUE;
16408         break;
16409       default:
16410         return FALSE;
16411     }
16412
16413     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16414     /* return the extra info in global variiables             */
16415
16416     /* set defaults in case FEN is incomplete */
16417     board[EP_STATUS] = EP_UNKNOWN;
16418     for(i=0; i<nrCastlingRights; i++ ) {
16419         board[CASTLING][i] =
16420             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16421     }   /* assume possible unless obviously impossible */
16422     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16423     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16424     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16425                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16426     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16427     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16428     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16429                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16430     FENrulePlies = 0;
16431
16432     while(*p==' ') p++;
16433     if(nrCastlingRights) {
16434       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16435           /* castling indicator present, so default becomes no castlings */
16436           for(i=0; i<nrCastlingRights; i++ ) {
16437                  board[CASTLING][i] = NoRights;
16438           }
16439       }
16440       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16441              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16442              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16443              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16444         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16445
16446         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16447             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16448             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16449         }
16450         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16451             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16452         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16453                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16454         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16455                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16456         switch(c) {
16457           case'K':
16458               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16459               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16460               board[CASTLING][2] = whiteKingFile;
16461               break;
16462           case'Q':
16463               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16464               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16465               board[CASTLING][2] = whiteKingFile;
16466               break;
16467           case'k':
16468               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16469               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16470               board[CASTLING][5] = blackKingFile;
16471               break;
16472           case'q':
16473               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16474               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16475               board[CASTLING][5] = blackKingFile;
16476           case '-':
16477               break;
16478           default: /* FRC castlings */
16479               if(c >= 'a') { /* black rights */
16480                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16481                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16482                   if(i == BOARD_RGHT) break;
16483                   board[CASTLING][5] = i;
16484                   c -= AAA;
16485                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16486                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16487                   if(c > i)
16488                       board[CASTLING][3] = c;
16489                   else
16490                       board[CASTLING][4] = c;
16491               } else { /* white rights */
16492                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16493                     if(board[0][i] == WhiteKing) break;
16494                   if(i == BOARD_RGHT) break;
16495                   board[CASTLING][2] = i;
16496                   c -= AAA - 'a' + 'A';
16497                   if(board[0][c] >= WhiteKing) break;
16498                   if(c > i)
16499                       board[CASTLING][0] = c;
16500                   else
16501                       board[CASTLING][1] = c;
16502               }
16503         }
16504       }
16505       for(i=0; i<nrCastlingRights; i++)
16506         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16507     if (appData.debugMode) {
16508         fprintf(debugFP, "FEN castling rights:");
16509         for(i=0; i<nrCastlingRights; i++)
16510         fprintf(debugFP, " %d", board[CASTLING][i]);
16511         fprintf(debugFP, "\n");
16512     }
16513
16514       while(*p==' ') p++;
16515     }
16516
16517     /* read e.p. field in games that know e.p. capture */
16518     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16519        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16520       if(*p=='-') {
16521         p++; board[EP_STATUS] = EP_NONE;
16522       } else {
16523          char c = *p++ - AAA;
16524
16525          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16526          if(*p >= '0' && *p <='9') p++;
16527          board[EP_STATUS] = c;
16528       }
16529     }
16530
16531
16532     if(sscanf(p, "%d", &i) == 1) {
16533         FENrulePlies = i; /* 50-move ply counter */
16534         /* (The move number is still ignored)    */
16535     }
16536
16537     return TRUE;
16538 }
16539
16540 void
16541 EditPositionPasteFEN(char *fen)
16542 {
16543   if (fen != NULL) {
16544     Board initial_position;
16545
16546     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16547       DisplayError(_("Bad FEN position in clipboard"), 0);
16548       return ;
16549     } else {
16550       int savedBlackPlaysFirst = blackPlaysFirst;
16551       EditPositionEvent();
16552       blackPlaysFirst = savedBlackPlaysFirst;
16553       CopyBoard(boards[0], initial_position);
16554       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16555       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16556       DisplayBothClocks();
16557       DrawPosition(FALSE, boards[currentMove]);
16558     }
16559   }
16560 }
16561
16562 static char cseq[12] = "\\   ";
16563
16564 Boolean set_cont_sequence(char *new_seq)
16565 {
16566     int len;
16567     Boolean ret;
16568
16569     // handle bad attempts to set the sequence
16570         if (!new_seq)
16571                 return 0; // acceptable error - no debug
16572
16573     len = strlen(new_seq);
16574     ret = (len > 0) && (len < sizeof(cseq));
16575     if (ret)
16576       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16577     else if (appData.debugMode)
16578       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16579     return ret;
16580 }
16581
16582 /*
16583     reformat a source message so words don't cross the width boundary.  internal
16584     newlines are not removed.  returns the wrapped size (no null character unless
16585     included in source message).  If dest is NULL, only calculate the size required
16586     for the dest buffer.  lp argument indicats line position upon entry, and it's
16587     passed back upon exit.
16588 */
16589 int wrap(char *dest, char *src, int count, int width, int *lp)
16590 {
16591     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16592
16593     cseq_len = strlen(cseq);
16594     old_line = line = *lp;
16595     ansi = len = clen = 0;
16596
16597     for (i=0; i < count; i++)
16598     {
16599         if (src[i] == '\033')
16600             ansi = 1;
16601
16602         // if we hit the width, back up
16603         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16604         {
16605             // store i & len in case the word is too long
16606             old_i = i, old_len = len;
16607
16608             // find the end of the last word
16609             while (i && src[i] != ' ' && src[i] != '\n')
16610             {
16611                 i--;
16612                 len--;
16613             }
16614
16615             // word too long?  restore i & len before splitting it
16616             if ((old_i-i+clen) >= width)
16617             {
16618                 i = old_i;
16619                 len = old_len;
16620             }
16621
16622             // extra space?
16623             if (i && src[i-1] == ' ')
16624                 len--;
16625
16626             if (src[i] != ' ' && src[i] != '\n')
16627             {
16628                 i--;
16629                 if (len)
16630                     len--;
16631             }
16632
16633             // now append the newline and continuation sequence
16634             if (dest)
16635                 dest[len] = '\n';
16636             len++;
16637             if (dest)
16638                 strncpy(dest+len, cseq, cseq_len);
16639             len += cseq_len;
16640             line = cseq_len;
16641             clen = cseq_len;
16642             continue;
16643         }
16644
16645         if (dest)
16646             dest[len] = src[i];
16647         len++;
16648         if (!ansi)
16649             line++;
16650         if (src[i] == '\n')
16651             line = 0;
16652         if (src[i] == 'm')
16653             ansi = 0;
16654     }
16655     if (dest && appData.debugMode)
16656     {
16657         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16658             count, width, line, len, *lp);
16659         show_bytes(debugFP, src, count);
16660         fprintf(debugFP, "\ndest: ");
16661         show_bytes(debugFP, dest, len);
16662         fprintf(debugFP, "\n");
16663     }
16664     *lp = dest ? line : old_line;
16665
16666     return len;
16667 }
16668
16669 // [HGM] vari: routines for shelving variations
16670 Boolean modeRestore = FALSE;
16671
16672 void
16673 PushInner(int firstMove, int lastMove)
16674 {
16675         int i, j, nrMoves = lastMove - firstMove;
16676
16677         // push current tail of game on stack
16678         savedResult[storedGames] = gameInfo.result;
16679         savedDetails[storedGames] = gameInfo.resultDetails;
16680         gameInfo.resultDetails = NULL;
16681         savedFirst[storedGames] = firstMove;
16682         savedLast [storedGames] = lastMove;
16683         savedFramePtr[storedGames] = framePtr;
16684         framePtr -= nrMoves; // reserve space for the boards
16685         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16686             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16687             for(j=0; j<MOVE_LEN; j++)
16688                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16689             for(j=0; j<2*MOVE_LEN; j++)
16690                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16691             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16692             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16693             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16694             pvInfoList[firstMove+i-1].depth = 0;
16695             commentList[framePtr+i] = commentList[firstMove+i];
16696             commentList[firstMove+i] = NULL;
16697         }
16698
16699         storedGames++;
16700         forwardMostMove = firstMove; // truncate game so we can start variation
16701 }
16702
16703 void
16704 PushTail(int firstMove, int lastMove)
16705 {
16706         if(appData.icsActive) { // only in local mode
16707                 forwardMostMove = currentMove; // mimic old ICS behavior
16708                 return;
16709         }
16710         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16711
16712         PushInner(firstMove, lastMove);
16713         if(storedGames == 1) GreyRevert(FALSE);
16714         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16715 }
16716
16717 void
16718 PopInner(Boolean annotate)
16719 {
16720         int i, j, nrMoves;
16721         char buf[8000], moveBuf[20];
16722
16723         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16724         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16725         nrMoves = savedLast[storedGames] - currentMove;
16726         if(annotate) {
16727                 int cnt = 10;
16728                 if(!WhiteOnMove(currentMove))
16729                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16730                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16731                 for(i=currentMove; i<forwardMostMove; i++) {
16732                         if(WhiteOnMove(i))
16733                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16734                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16735                         strcat(buf, moveBuf);
16736                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16737                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16738                 }
16739                 strcat(buf, ")");
16740         }
16741         for(i=1; i<=nrMoves; i++) { // copy last variation back
16742             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16743             for(j=0; j<MOVE_LEN; j++)
16744                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16745             for(j=0; j<2*MOVE_LEN; j++)
16746                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16747             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16748             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16749             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16750             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16751             commentList[currentMove+i] = commentList[framePtr+i];
16752             commentList[framePtr+i] = NULL;
16753         }
16754         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16755         framePtr = savedFramePtr[storedGames];
16756         gameInfo.result = savedResult[storedGames];
16757         if(gameInfo.resultDetails != NULL) {
16758             free(gameInfo.resultDetails);
16759       }
16760         gameInfo.resultDetails = savedDetails[storedGames];
16761         forwardMostMove = currentMove + nrMoves;
16762 }
16763
16764 Boolean
16765 PopTail(Boolean annotate)
16766 {
16767         if(appData.icsActive) return FALSE; // only in local mode
16768         if(!storedGames) return FALSE; // sanity
16769         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16770
16771         PopInner(annotate);
16772         if(currentMove < forwardMostMove) ForwardEvent(); else
16773         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16774
16775         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16776         return TRUE;
16777 }
16778
16779 void
16780 CleanupTail()
16781 {       // remove all shelved variations
16782         int i;
16783         for(i=0; i<storedGames; i++) {
16784             if(savedDetails[i])
16785                 free(savedDetails[i]);
16786             savedDetails[i] = NULL;
16787         }
16788         for(i=framePtr; i<MAX_MOVES; i++) {
16789                 if(commentList[i]) free(commentList[i]);
16790                 commentList[i] = NULL;
16791         }
16792         framePtr = MAX_MOVES-1;
16793         storedGames = 0;
16794 }
16795
16796 void
16797 LoadVariation(int index, char *text)
16798 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16799         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16800         int level = 0, move;
16801
16802         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16803         // first find outermost bracketing variation
16804         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16805             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16806                 if(*p == '{') wait = '}'; else
16807                 if(*p == '[') wait = ']'; else
16808                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16809                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16810             }
16811             if(*p == wait) wait = NULLCHAR; // closing ]} found
16812             p++;
16813         }
16814         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16815         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16816         end[1] = NULLCHAR; // clip off comment beyond variation
16817         ToNrEvent(currentMove-1);
16818         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16819         // kludge: use ParsePV() to append variation to game
16820         move = currentMove;
16821         ParsePV(start, TRUE, TRUE);
16822         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16823         ClearPremoveHighlights();
16824         CommentPopDown();
16825         ToNrEvent(currentMove+1);
16826 }
16827