891621148b9204fbae16214195fe0bf273c47204
[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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
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     flags &= ~F_ALL_CASTLE_OK;
398     break;
399   default:
400     break;
401   }
402   return flags;
403 }
404
405 FILE *gameFileFP, *debugFP;
406
407 /*
408     [AS] Note: sometimes, the sscanf() function is used to parse the input
409     into a fixed-size buffer. Because of this, we must be prepared to
410     receive strings as long as the size of the input buffer, which is currently
411     set to 4K for Windows and 8K for the rest.
412     So, we must either allocate sufficiently large buffers here, or
413     reduce the size of the input buffer in the input reading part.
414 */
415
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
419
420 ChessProgramState first, second;
421
422 /* premove variables */
423 int premoveToX = 0;
424 int premoveToY = 0;
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
428 int gotPremove = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
431
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
434
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
462
463 int have_sent_ICS_logon = 0;
464 int movesPerSession;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
476 /* animateTraining preserves the state of appData.animate
477  * when Training mode is activated. This allows the
478  * response to be animated when appData.animate == TRUE and
479  * appData.animateDragging == TRUE.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char  initialRights[BOARD_FILES];
491 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int   initialRulePlies, FENrulePlies;
493 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 int loadFlag = 0;
495 int shuffleOpenings;
496 int mute; // mute all sounds
497
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int storedGames = 0;
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
507
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
513
514 ChessSquare  FIDEArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackBishop, BlackKnight, BlackRook }
519 };
520
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525         BlackKing, BlackKing, BlackKnight, BlackRook }
526 };
527
528 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531     { BlackRook, BlackMan, BlackBishop, BlackQueen,
532         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
533 };
534
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
540 };
541
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
547 };
548
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
554 };
555
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackMan, BlackFerz,
560         BlackKing, BlackMan, BlackKnight, BlackRook }
561 };
562
563
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
570 };
571
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
577 };
578
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
584 };
585
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
591 };
592
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating(str)
649   char *str;
650 {
651   while(*str && !isdigit(*str)) ++str;
652   if (!*str)
653     return 0;   /* One of the special "no rating" cases */
654   else
655     return atoi(str);
656 }
657
658 void
659 ClearProgramStats()
660 {
661     /* Init programStats */
662     programStats.movelist[0] = 0;
663     programStats.depth = 0;
664     programStats.nr_moves = 0;
665     programStats.moves_left = 0;
666     programStats.nodes = 0;
667     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
668     programStats.score = 0;
669     programStats.got_only_move = 0;
670     programStats.got_fail = 0;
671     programStats.line_is_book = 0;
672 }
673
674 void
675 CommonEngineInit()
676 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677     if (appData.firstPlaysBlack) {
678         first.twoMachinesColor = "black\n";
679         second.twoMachinesColor = "white\n";
680     } else {
681         first.twoMachinesColor = "white\n";
682         second.twoMachinesColor = "black\n";
683     }
684
685     first.other = &second;
686     second.other = &first;
687
688     { float norm = 1;
689         if(appData.timeOddsMode) {
690             norm = appData.timeOdds[0];
691             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692         }
693         first.timeOdds  = appData.timeOdds[0]/norm;
694         second.timeOdds = appData.timeOdds[1]/norm;
695     }
696
697     if(programVersion) free(programVersion);
698     if (appData.noChessProgram) {
699         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700         sprintf(programVersion, "%s", PACKAGE_STRING);
701     } else {
702       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
705     }
706 }
707
708 void
709 UnloadEngine(ChessProgramState *cps)
710 {
711         /* Kill off first chess program */
712         if (cps->isr != NULL)
713           RemoveInputSource(cps->isr);
714         cps->isr = NULL;
715
716         if (cps->pr != NoProc) {
717             ExitAnalyzeMode();
718             DoSleep( appData.delayBeforeQuit );
719             SendToProgram("quit\n", cps);
720             DoSleep( appData.delayAfterQuit );
721             DestroyChildProcess(cps->pr, cps->useSigterm);
722         }
723         cps->pr = NoProc;
724         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 }
726
727 void
728 ClearOptions(ChessProgramState *cps)
729 {
730     int i;
731     cps->nrOptions = cps->comboCnt = 0;
732     for(i=0; i<MAX_OPTIONS; i++) {
733         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734         cps->option[i].textValue = 0;
735     }
736 }
737
738 char *engineNames[] = {
739 "first",
740 "second"
741 };
742
743 void
744 InitEngine(ChessProgramState *cps, int n)
745 {   // [HGM] all engine initialiation put in a function that does one engine
746
747     ClearOptions(cps);
748
749     cps->which = engineNames[n];
750     cps->maybeThinking = FALSE;
751     cps->pr = NoProc;
752     cps->isr = NULL;
753     cps->sendTime = 2;
754     cps->sendDrawOffers = 1;
755
756     cps->program = appData.chessProgram[n];
757     cps->host = appData.host[n];
758     cps->dir = appData.directory[n];
759     cps->initString = appData.engInitString[n];
760     cps->computerString = appData.computerString[n];
761     cps->useSigint  = TRUE;
762     cps->useSigterm = TRUE;
763     cps->reuse = appData.reuse[n];
764     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
765     cps->useSetboard = FALSE;
766     cps->useSAN = FALSE;
767     cps->usePing = FALSE;
768     cps->lastPing = 0;
769     cps->lastPong = 0;
770     cps->usePlayother = FALSE;
771     cps->useColors = TRUE;
772     cps->useUsermove = FALSE;
773     cps->sendICS = FALSE;
774     cps->sendName = appData.icsActive;
775     cps->sdKludge = FALSE;
776     cps->stKludge = FALSE;
777     TidyProgramName(cps->program, cps->host, cps->tidy);
778     cps->matchWins = 0;
779     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780     cps->analysisSupport = 2; /* detect */
781     cps->analyzing = FALSE;
782     cps->initDone = FALSE;
783
784     /* New features added by Tord: */
785     cps->useFEN960 = FALSE;
786     cps->useOOCastle = TRUE;
787     /* End of new features added by Tord. */
788     cps->fenOverride  = appData.fenOverride[n];
789
790     /* [HGM] time odds: set factor for each machine */
791     cps->timeOdds  = appData.timeOdds[n];
792
793     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794     cps->accumulateTC = appData.accumulateTC[n];
795     cps->maxNrOfSessions = 1;
796
797     /* [HGM] debug */
798     cps->debug = FALSE;
799
800     cps->supportsNPS = UNKNOWN;
801     cps->memSize = FALSE;
802     cps->maxCores = FALSE;
803     cps->egtFormats[0] = NULLCHAR;
804
805     /* [HGM] options */
806     cps->optionSettings  = appData.engOptions[n];
807
808     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
809     cps->isUCI = appData.isUCI[n]; /* [AS] */
810     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
811
812     if (appData.protocolVersion[n] > PROTOVER
813         || appData.protocolVersion[n] < 1)
814       {
815         char buf[MSG_SIZ];
816         int len;
817
818         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
819                        appData.protocolVersion[n]);
820         if( (len > MSG_SIZ) && appData.debugMode )
821           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
822
823         DisplayFatalError(buf, 0, 2);
824       }
825     else
826       {
827         cps->protocolVersion = appData.protocolVersion[n];
828       }
829
830     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
831 }
832
833 ChessProgramState *savCps;
834
835 void
836 LoadEngine()
837 {
838     int i;
839     if(WaitForEngine(savCps, LoadEngine)) return;
840     CommonEngineInit(); // recalculate time odds
841     if(gameInfo.variant != StringToVariant(appData.variant)) {
842         // we changed variant when loading the engine; this forces us to reset
843         Reset(TRUE, savCps != &first);
844         EditGameEvent(); // for consistency with other path, as Reset changes mode
845     }
846     InitChessProgram(savCps, FALSE);
847     SendToProgram("force\n", savCps);
848     DisplayMessage("", "");
849     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
850     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
851     ThawUI();
852     SetGNUMode();
853 }
854
855 void
856 ReplaceEngine(ChessProgramState *cps, int n)
857 {
858     EditGameEvent();
859     UnloadEngine(cps);
860     appData.noChessProgram = FALSE;
861     appData.clockMode = TRUE;
862     InitEngine(cps, n);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load(ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
879     if(engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
883         ParseArgsFromString(buf);
884         SwapEngines(i);
885         ReplaceEngine(cps, i);
886         return;
887     }
888     p = engineName;
889     while(q = strchr(p, SLASH)) p = q+1;
890     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891     if(engineDir[0] != NULLCHAR)
892         appData.directory[i] = engineDir;
893     else if(p != engineName) { // derive directory from engine path, when not given
894         p[-1] = 0;
895         appData.directory[i] = strdup(engineName);
896         p[-1] = SLASH;
897     } else appData.directory[i] = ".";
898     if(params[0]) {
899         snprintf(command, MSG_SIZ, "%s %s", p, params);
900         p = command;
901     }
902     appData.chessProgram[i] = strdup(p);
903     appData.isUCI[i] = isUCI;
904     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
905     appData.hasOwnBookUCI[i] = hasBook;
906     if(!nickName[0]) useNick = FALSE;
907     if(useNick) ASSIGN(appData.pgnName[i], nickName);
908     if(addToList) {
909         int len;
910         q = firstChessProgramNames;
911         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
912         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
913                         useNick ? " -fn \"" : "",
914                         useNick ? nickName : "",
915                         useNick ? "\"" : "",
916                         v1 ? " -firstProtocolVersion 1" : "",
917                         hasBook ? "" : " -fNoOwnBookUCI",
918                         isUCI ? " -fUCI" : "",
919                         storeVariant ? " -variant " : "",
920                         storeVariant ? VariantName(gameInfo.variant) : "");
921         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
922         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
923         if(q)   free(q);
924     }
925     ReplaceEngine(cps, i);
926 }
927
928 void
929 InitTimeControls()
930 {
931     int matched, min, sec;
932     /*
933      * Parse timeControl resource
934      */
935     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
936                           appData.movesPerSession)) {
937         char buf[MSG_SIZ];
938         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
939         DisplayFatalError(buf, 0, 2);
940     }
941
942     /*
943      * Parse searchTime resource
944      */
945     if (*appData.searchTime != NULLCHAR) {
946         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
947         if (matched == 1) {
948             searchTime = min * 60;
949         } else if (matched == 2) {
950             searchTime = min * 60 + sec;
951         } else {
952             char buf[MSG_SIZ];
953             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
954             DisplayFatalError(buf, 0, 2);
955         }
956     }
957 }
958
959 void
960 InitBackEnd1()
961 {
962
963     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
964     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
965
966     GetTimeMark(&programStartTime);
967     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
968     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
969
970     ClearProgramStats();
971     programStats.ok_to_send = 1;
972     programStats.seen_stat = 0;
973
974     /*
975      * Initialize game list
976      */
977     ListNew(&gameList);
978
979
980     /*
981      * Internet chess server status
982      */
983     if (appData.icsActive) {
984         appData.matchMode = FALSE;
985         appData.matchGames = 0;
986 #if ZIPPY
987         appData.noChessProgram = !appData.zippyPlay;
988 #else
989         appData.zippyPlay = FALSE;
990         appData.zippyTalk = FALSE;
991         appData.noChessProgram = TRUE;
992 #endif
993         if (*appData.icsHelper != NULLCHAR) {
994             appData.useTelnet = TRUE;
995             appData.telnetProgram = appData.icsHelper;
996         }
997     } else {
998         appData.zippyTalk = appData.zippyPlay = FALSE;
999     }
1000
1001     /* [AS] Initialize pv info list [HGM] and game state */
1002     {
1003         int i, j;
1004
1005         for( i=0; i<=framePtr; i++ ) {
1006             pvInfoList[i].depth = -1;
1007             boards[i][EP_STATUS] = EP_NONE;
1008             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1009         }
1010     }
1011
1012     InitTimeControls();
1013
1014     /* [AS] Adjudication threshold */
1015     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1016
1017     InitEngine(&first, 0);
1018     InitEngine(&second, 1);
1019     CommonEngineInit();
1020
1021     if (appData.icsActive) {
1022         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1023     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1024         appData.clockMode = FALSE;
1025         first.sendTime = second.sendTime = 0;
1026     }
1027
1028 #if ZIPPY
1029     /* Override some settings from environment variables, for backward
1030        compatibility.  Unfortunately it's not feasible to have the env
1031        vars just set defaults, at least in xboard.  Ugh.
1032     */
1033     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1034       ZippyInit();
1035     }
1036 #endif
1037
1038     if (!appData.icsActive) {
1039       char buf[MSG_SIZ];
1040       int len;
1041
1042       /* Check for variants that are supported only in ICS mode,
1043          or not at all.  Some that are accepted here nevertheless
1044          have bugs; see comments below.
1045       */
1046       VariantClass variant = StringToVariant(appData.variant);
1047       switch (variant) {
1048       case VariantBughouse:     /* need four players and two boards */
1049       case VariantKriegspiel:   /* need to hide pieces and move details */
1050         /* case VariantFischeRandom: (Fabien: moved below) */
1051         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1052         if( (len > MSG_SIZ) && appData.debugMode )
1053           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1054
1055         DisplayFatalError(buf, 0, 2);
1056         return;
1057
1058       case VariantUnknown:
1059       case VariantLoadable:
1060       case Variant29:
1061       case Variant30:
1062       case Variant31:
1063       case Variant32:
1064       case Variant33:
1065       case Variant34:
1066       case Variant35:
1067       case Variant36:
1068       default:
1069         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1070         if( (len > MSG_SIZ) && appData.debugMode )
1071           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1072
1073         DisplayFatalError(buf, 0, 2);
1074         return;
1075
1076       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1077       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1078       case VariantGothic:     /* [HGM] should work */
1079       case VariantCapablanca: /* [HGM] should work */
1080       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1081       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1082       case VariantKnightmate: /* [HGM] should work */
1083       case VariantCylinder:   /* [HGM] untested */
1084       case VariantFalcon:     /* [HGM] untested */
1085       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1086                                  offboard interposition not understood */
1087       case VariantNormal:     /* definitely works! */
1088       case VariantWildCastle: /* pieces not automatically shuffled */
1089       case VariantNoCastle:   /* pieces not automatically shuffled */
1090       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1091       case VariantLosers:     /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantSuicide:    /* should work except for win condition,
1094                                  and doesn't know captures are mandatory */
1095       case VariantGiveaway:   /* should work except for win condition,
1096                                  and doesn't know captures are mandatory */
1097       case VariantTwoKings:   /* should work */
1098       case VariantAtomic:     /* should work except for win condition */
1099       case Variant3Check:     /* should work except for win condition */
1100       case VariantShatranj:   /* should work except for all win conditions */
1101       case VariantMakruk:     /* should work except for daw countdown */
1102       case VariantBerolina:   /* might work if TestLegality is off */
1103       case VariantCapaRandom: /* should work */
1104       case VariantJanus:      /* should work */
1105       case VariantSuper:      /* experimental */
1106       case VariantGreat:      /* experimental, requires legality testing to be off */
1107       case VariantSChess:     /* S-Chess, should work */
1108       case VariantSpartan:    /* should work */
1109         break;
1110       }
1111     }
1112
1113 }
1114
1115 int NextIntegerFromString( char ** str, long * value )
1116 {
1117     int result = -1;
1118     char * s = *str;
1119
1120     while( *s == ' ' || *s == '\t' ) {
1121         s++;
1122     }
1123
1124     *value = 0;
1125
1126     if( *s >= '0' && *s <= '9' ) {
1127         while( *s >= '0' && *s <= '9' ) {
1128             *value = *value * 10 + (*s - '0');
1129             s++;
1130         }
1131
1132         result = 0;
1133     }
1134
1135     *str = s;
1136
1137     return result;
1138 }
1139
1140 int NextTimeControlFromString( char ** str, long * value )
1141 {
1142     long temp;
1143     int result = NextIntegerFromString( str, &temp );
1144
1145     if( result == 0 ) {
1146         *value = temp * 60; /* Minutes */
1147         if( **str == ':' ) {
1148             (*str)++;
1149             result = NextIntegerFromString( str, &temp );
1150             *value += temp; /* Seconds */
1151         }
1152     }
1153
1154     return result;
1155 }
1156
1157 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1158 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1159     int result = -1, type = 0; long temp, temp2;
1160
1161     if(**str != ':') return -1; // old params remain in force!
1162     (*str)++;
1163     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1164     if( NextIntegerFromString( str, &temp ) ) return -1;
1165     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1166
1167     if(**str != '/') {
1168         /* time only: incremental or sudden-death time control */
1169         if(**str == '+') { /* increment follows; read it */
1170             (*str)++;
1171             if(**str == '!') type = *(*str)++; // Bronstein TC
1172             if(result = NextIntegerFromString( str, &temp2)) return -1;
1173             *inc = temp2 * 1000;
1174             if(**str == '.') { // read fraction of increment
1175                 char *start = ++(*str);
1176                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1177                 temp2 *= 1000;
1178                 while(start++ < *str) temp2 /= 10;
1179                 *inc += temp2;
1180             }
1181         } else *inc = 0;
1182         *moves = 0; *tc = temp * 1000; *incType = type;
1183         return 0;
1184     }
1185
1186     (*str)++; /* classical time control */
1187     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1188
1189     if(result == 0) {
1190         *moves = temp;
1191         *tc    = temp2 * 1000;
1192         *inc   = 0;
1193         *incType = type;
1194     }
1195     return result;
1196 }
1197
1198 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1199 {   /* [HGM] get time to add from the multi-session time-control string */
1200     int incType, moves=1; /* kludge to force reading of first session */
1201     long time, increment;
1202     char *s = tcString;
1203
1204     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1205     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1206     do {
1207         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1208         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1209         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1210         if(movenr == -1) return time;    /* last move before new session     */
1211         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1212         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1213         if(!moves) return increment;     /* current session is incremental   */
1214         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1215     } while(movenr >= -1);               /* try again for next session       */
1216
1217     return 0; // no new time quota on this move
1218 }
1219
1220 int
1221 ParseTimeControl(tc, ti, mps)
1222      char *tc;
1223      float ti;
1224      int mps;
1225 {
1226   long tc1;
1227   long tc2;
1228   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1229   int min, sec=0;
1230
1231   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1232   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1233       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1234   if(ti > 0) {
1235
1236     if(mps)
1237       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1238     else 
1239       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1240   } else {
1241     if(mps)
1242       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1243     else 
1244       snprintf(buf, MSG_SIZ, ":%s", mytc);
1245   }
1246   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1247   
1248   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1249     return FALSE;
1250   }
1251
1252   if( *tc == '/' ) {
1253     /* Parse second time control */
1254     tc++;
1255
1256     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1257       return FALSE;
1258     }
1259
1260     if( tc2 == 0 ) {
1261       return FALSE;
1262     }
1263
1264     timeControl_2 = tc2 * 1000;
1265   }
1266   else {
1267     timeControl_2 = 0;
1268   }
1269
1270   if( tc1 == 0 ) {
1271     return FALSE;
1272   }
1273
1274   timeControl = tc1 * 1000;
1275
1276   if (ti >= 0) {
1277     timeIncrement = ti * 1000;  /* convert to ms */
1278     movesPerSession = 0;
1279   } else {
1280     timeIncrement = 0;
1281     movesPerSession = mps;
1282   }
1283   return TRUE;
1284 }
1285
1286 void
1287 InitBackEnd2()
1288 {
1289     if (appData.debugMode) {
1290         fprintf(debugFP, "%s\n", programVersion);
1291     }
1292
1293     set_cont_sequence(appData.wrapContSeq);
1294     if (appData.matchGames > 0) {
1295         appData.matchMode = TRUE;
1296     } else if (appData.matchMode) {
1297         appData.matchGames = 1;
1298     }
1299     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1300         appData.matchGames = appData.sameColorGames;
1301     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1302         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1303         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1304     }
1305     Reset(TRUE, FALSE);
1306     if (appData.noChessProgram || first.protocolVersion == 1) {
1307       InitBackEnd3();
1308     } else {
1309       /* kludge: allow timeout for initial "feature" commands */
1310       FreezeUI();
1311       DisplayMessage("", _("Starting chess program"));
1312       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1313     }
1314 }
1315
1316 int
1317 CalculateIndex(int index, int gameNr)
1318 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1319     int res;
1320     if(index > 0) return index; // fixed nmber
1321     if(index == 0) return 1;
1322     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1323     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1324     return res;
1325 }
1326
1327 int
1328 LoadGameOrPosition(int gameNr)
1329 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1330     if (*appData.loadGameFile != NULLCHAR) {
1331         if (!LoadGameFromFile(appData.loadGameFile,
1332                 CalculateIndex(appData.loadGameIndex, gameNr),
1333                               appData.loadGameFile, FALSE)) {
1334             DisplayFatalError(_("Bad game file"), 0, 1);
1335             return 0;
1336         }
1337     } else if (*appData.loadPositionFile != NULLCHAR) {
1338         if (!LoadPositionFromFile(appData.loadPositionFile,
1339                 CalculateIndex(appData.loadPositionIndex, gameNr),
1340                                   appData.loadPositionFile)) {
1341             DisplayFatalError(_("Bad position file"), 0, 1);
1342             return 0;
1343         }
1344     }
1345     return 1;
1346 }
1347
1348 void
1349 ReserveGame(int gameNr, char resChar)
1350 {
1351     FILE *tf = fopen(appData.tourneyFile, "r+");
1352     char *p, *q, c, buf[MSG_SIZ];
1353     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1354     safeStrCpy(buf, lastMsg, MSG_SIZ);
1355     DisplayMessage(_("Pick new game"), "");
1356     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1357     ParseArgsFromFile(tf);
1358     p = q = appData.results;
1359     if(appData.debugMode) {
1360       char *r = appData.participants;
1361       fprintf(debugFP, "results = '%s'\n", p);
1362       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1363       fprintf(debugFP, "\n");
1364     }
1365     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1366     nextGame = q - p;
1367     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1368     safeStrCpy(q, p, strlen(p) + 2);
1369     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1370     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1371     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1372         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1373         q[nextGame] = '*';
1374     }
1375     fseek(tf, -(strlen(p)+4), SEEK_END);
1376     c = fgetc(tf);
1377     if(c != '"') // depending on DOS or Unix line endings we can be one off
1378          fseek(tf, -(strlen(p)+2), SEEK_END);
1379     else fseek(tf, -(strlen(p)+3), SEEK_END);
1380     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1381     DisplayMessage(buf, "");
1382     free(p); appData.results = q;
1383     if(nextGame <= appData.matchGames && resChar != ' ' &&
1384        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1385         UnloadEngine(&first);  // next game belongs to other pairing;
1386         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1387     }
1388 }
1389
1390 void
1391 MatchEvent(int mode)
1392 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1393         int dummy;
1394         if(matchMode) { // already in match mode: switch it off
1395             abortMatch = TRUE;
1396             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1397             ModeHighlight(); // kludgey way to remove checkmark...
1398             return;
1399         }
1400 //      if(gameMode != BeginningOfGame) {
1401 //          DisplayError(_("You can only start a match from the initial position."), 0);
1402 //          return;
1403 //      }
1404         abortMatch = FALSE;
1405         appData.matchGames = appData.defaultMatchGames;
1406         /* Set up machine vs. machine match */
1407         nextGame = 0;
1408         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1409         if(appData.tourneyFile[0]) {
1410             ReserveGame(-1, 0);
1411             if(nextGame > appData.matchGames) {
1412                 char buf[MSG_SIZ];
1413                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1414                 DisplayError(buf, 0);
1415                 appData.tourneyFile[0] = 0;
1416                 return;
1417             }
1418         } else
1419         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1420             DisplayFatalError(_("Can't have a match with no chess programs"),
1421                               0, 2);
1422             return;
1423         }
1424         matchMode = mode;
1425         matchGame = roundNr = 1;
1426         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1427         NextMatchGame();
1428 }
1429
1430 void
1431 InitBackEnd3 P((void))
1432 {
1433     GameMode initialMode;
1434     char buf[MSG_SIZ];
1435     int err, len;
1436
1437     InitChessProgram(&first, startedFromSetupPosition);
1438
1439     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1440         free(programVersion);
1441         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1442         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1443     }
1444
1445     if (appData.icsActive) {
1446 #ifdef WIN32
1447         /* [DM] Make a console window if needed [HGM] merged ifs */
1448         ConsoleCreate();
1449 #endif
1450         err = establish();
1451         if (err != 0)
1452           {
1453             if (*appData.icsCommPort != NULLCHAR)
1454               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1455                              appData.icsCommPort);
1456             else
1457               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1458                         appData.icsHost, appData.icsPort);
1459
1460             if( (len > MSG_SIZ) && appData.debugMode )
1461               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1462
1463             DisplayFatalError(buf, err, 1);
1464             return;
1465         }
1466         SetICSMode();
1467         telnetISR =
1468           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1469         fromUserISR =
1470           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1471         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1472             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1473     } else if (appData.noChessProgram) {
1474         SetNCPMode();
1475     } else {
1476         SetGNUMode();
1477     }
1478
1479     if (*appData.cmailGameName != NULLCHAR) {
1480         SetCmailMode();
1481         OpenLoopback(&cmailPR);
1482         cmailISR =
1483           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1484     }
1485
1486     ThawUI();
1487     DisplayMessage("", "");
1488     if (StrCaseCmp(appData.initialMode, "") == 0) {
1489       initialMode = BeginningOfGame;
1490       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1491         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1492         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1493         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1494         ModeHighlight();
1495       }
1496     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1497       initialMode = TwoMachinesPlay;
1498     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1499       initialMode = AnalyzeFile;
1500     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1501       initialMode = AnalyzeMode;
1502     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1503       initialMode = MachinePlaysWhite;
1504     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1505       initialMode = MachinePlaysBlack;
1506     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1507       initialMode = EditGame;
1508     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1509       initialMode = EditPosition;
1510     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1511       initialMode = Training;
1512     } else {
1513       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1514       if( (len > MSG_SIZ) && appData.debugMode )
1515         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1516
1517       DisplayFatalError(buf, 0, 2);
1518       return;
1519     }
1520
1521     if (appData.matchMode) {
1522         if(appData.tourneyFile[0]) { // start tourney from command line
1523             FILE *f;
1524             if(f = fopen(appData.tourneyFile, "r")) {
1525                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1526                 fclose(f);
1527             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1528         }
1529         MatchEvent(TRUE);
1530     } else if (*appData.cmailGameName != NULLCHAR) {
1531         /* Set up cmail mode */
1532         ReloadCmailMsgEvent(TRUE);
1533     } else {
1534         /* Set up other modes */
1535         if (initialMode == AnalyzeFile) {
1536           if (*appData.loadGameFile == NULLCHAR) {
1537             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1538             return;
1539           }
1540         }
1541         if (*appData.loadGameFile != NULLCHAR) {
1542             (void) LoadGameFromFile(appData.loadGameFile,
1543                                     appData.loadGameIndex,
1544                                     appData.loadGameFile, TRUE);
1545         } else if (*appData.loadPositionFile != NULLCHAR) {
1546             (void) LoadPositionFromFile(appData.loadPositionFile,
1547                                         appData.loadPositionIndex,
1548                                         appData.loadPositionFile);
1549             /* [HGM] try to make self-starting even after FEN load */
1550             /* to allow automatic setup of fairy variants with wtm */
1551             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1552                 gameMode = BeginningOfGame;
1553                 setboardSpoiledMachineBlack = 1;
1554             }
1555             /* [HGM] loadPos: make that every new game uses the setup */
1556             /* from file as long as we do not switch variant          */
1557             if(!blackPlaysFirst) {
1558                 startedFromPositionFile = TRUE;
1559                 CopyBoard(filePosition, boards[0]);
1560             }
1561         }
1562         if (initialMode == AnalyzeMode) {
1563           if (appData.noChessProgram) {
1564             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1565             return;
1566           }
1567           if (appData.icsActive) {
1568             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1569             return;
1570           }
1571           AnalyzeModeEvent();
1572         } else if (initialMode == AnalyzeFile) {
1573           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1574           ShowThinkingEvent();
1575           AnalyzeFileEvent();
1576           AnalysisPeriodicEvent(1);
1577         } else if (initialMode == MachinePlaysWhite) {
1578           if (appData.noChessProgram) {
1579             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1580                               0, 2);
1581             return;
1582           }
1583           if (appData.icsActive) {
1584             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1585                               0, 2);
1586             return;
1587           }
1588           MachineWhiteEvent();
1589         } else if (initialMode == MachinePlaysBlack) {
1590           if (appData.noChessProgram) {
1591             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1592                               0, 2);
1593             return;
1594           }
1595           if (appData.icsActive) {
1596             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1597                               0, 2);
1598             return;
1599           }
1600           MachineBlackEvent();
1601         } else if (initialMode == TwoMachinesPlay) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1604                               0, 2);
1605             return;
1606           }
1607           if (appData.icsActive) {
1608             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1609                               0, 2);
1610             return;
1611           }
1612           TwoMachinesEvent();
1613         } else if (initialMode == EditGame) {
1614           EditGameEvent();
1615         } else if (initialMode == EditPosition) {
1616           EditPositionEvent();
1617         } else if (initialMode == Training) {
1618           if (*appData.loadGameFile == NULLCHAR) {
1619             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1620             return;
1621           }
1622           TrainingEvent();
1623         }
1624     }
1625 }
1626
1627 /*
1628  * Establish will establish a contact to a remote host.port.
1629  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1630  *  used to talk to the host.
1631  * Returns 0 if okay, error code if not.
1632  */
1633 int
1634 establish()
1635 {
1636     char buf[MSG_SIZ];
1637
1638     if (*appData.icsCommPort != NULLCHAR) {
1639         /* Talk to the host through a serial comm port */
1640         return OpenCommPort(appData.icsCommPort, &icsPR);
1641
1642     } else if (*appData.gateway != NULLCHAR) {
1643         if (*appData.remoteShell == NULLCHAR) {
1644             /* Use the rcmd protocol to run telnet program on a gateway host */
1645             snprintf(buf, sizeof(buf), "%s %s %s",
1646                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1647             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1648
1649         } else {
1650             /* Use the rsh program to run telnet program on a gateway host */
1651             if (*appData.remoteUser == NULLCHAR) {
1652                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1653                         appData.gateway, appData.telnetProgram,
1654                         appData.icsHost, appData.icsPort);
1655             } else {
1656                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1657                         appData.remoteShell, appData.gateway,
1658                         appData.remoteUser, appData.telnetProgram,
1659                         appData.icsHost, appData.icsPort);
1660             }
1661             return StartChildProcess(buf, "", &icsPR);
1662
1663         }
1664     } else if (appData.useTelnet) {
1665         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1666
1667     } else {
1668         /* TCP socket interface differs somewhat between
1669            Unix and NT; handle details in the front end.
1670            */
1671         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1672     }
1673 }
1674
1675 void EscapeExpand(char *p, char *q)
1676 {       // [HGM] initstring: routine to shape up string arguments
1677         while(*p++ = *q++) if(p[-1] == '\\')
1678             switch(*q++) {
1679                 case 'n': p[-1] = '\n'; break;
1680                 case 'r': p[-1] = '\r'; break;
1681                 case 't': p[-1] = '\t'; break;
1682                 case '\\': p[-1] = '\\'; break;
1683                 case 0: *p = 0; return;
1684                 default: p[-1] = q[-1]; break;
1685             }
1686 }
1687
1688 void
1689 show_bytes(fp, buf, count)
1690      FILE *fp;
1691      char *buf;
1692      int count;
1693 {
1694     while (count--) {
1695         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1696             fprintf(fp, "\\%03o", *buf & 0xff);
1697         } else {
1698             putc(*buf, fp);
1699         }
1700         buf++;
1701     }
1702     fflush(fp);
1703 }
1704
1705 /* Returns an errno value */
1706 int
1707 OutputMaybeTelnet(pr, message, count, outError)
1708      ProcRef pr;
1709      char *message;
1710      int count;
1711      int *outError;
1712 {
1713     char buf[8192], *p, *q, *buflim;
1714     int left, newcount, outcount;
1715
1716     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1717         *appData.gateway != NULLCHAR) {
1718         if (appData.debugMode) {
1719             fprintf(debugFP, ">ICS: ");
1720             show_bytes(debugFP, message, count);
1721             fprintf(debugFP, "\n");
1722         }
1723         return OutputToProcess(pr, message, count, outError);
1724     }
1725
1726     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1727     p = message;
1728     q = buf;
1729     left = count;
1730     newcount = 0;
1731     while (left) {
1732         if (q >= buflim) {
1733             if (appData.debugMode) {
1734                 fprintf(debugFP, ">ICS: ");
1735                 show_bytes(debugFP, buf, newcount);
1736                 fprintf(debugFP, "\n");
1737             }
1738             outcount = OutputToProcess(pr, buf, newcount, outError);
1739             if (outcount < newcount) return -1; /* to be sure */
1740             q = buf;
1741             newcount = 0;
1742         }
1743         if (*p == '\n') {
1744             *q++ = '\r';
1745             newcount++;
1746         } else if (((unsigned char) *p) == TN_IAC) {
1747             *q++ = (char) TN_IAC;
1748             newcount ++;
1749         }
1750         *q++ = *p++;
1751         newcount++;
1752         left--;
1753     }
1754     if (appData.debugMode) {
1755         fprintf(debugFP, ">ICS: ");
1756         show_bytes(debugFP, buf, newcount);
1757         fprintf(debugFP, "\n");
1758     }
1759     outcount = OutputToProcess(pr, buf, newcount, outError);
1760     if (outcount < newcount) return -1; /* to be sure */
1761     return count;
1762 }
1763
1764 void
1765 read_from_player(isr, closure, message, count, error)
1766      InputSourceRef isr;
1767      VOIDSTAR closure;
1768      char *message;
1769      int count;
1770      int error;
1771 {
1772     int outError, outCount;
1773     static int gotEof = 0;
1774
1775     /* Pass data read from player on to ICS */
1776     if (count > 0) {
1777         gotEof = 0;
1778         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1779         if (outCount < count) {
1780             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1781         }
1782     } else if (count < 0) {
1783         RemoveInputSource(isr);
1784         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1785     } else if (gotEof++ > 0) {
1786         RemoveInputSource(isr);
1787         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1788     }
1789 }
1790
1791 void
1792 KeepAlive()
1793 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1794     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1795     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1796     SendToICS("date\n");
1797     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1798 }
1799
1800 /* added routine for printf style output to ics */
1801 void ics_printf(char *format, ...)
1802 {
1803     char buffer[MSG_SIZ];
1804     va_list args;
1805
1806     va_start(args, format);
1807     vsnprintf(buffer, sizeof(buffer), format, args);
1808     buffer[sizeof(buffer)-1] = '\0';
1809     SendToICS(buffer);
1810     va_end(args);
1811 }
1812
1813 void
1814 SendToICS(s)
1815      char *s;
1816 {
1817     int count, outCount, outError;
1818
1819     if (icsPR == NULL) return;
1820
1821     count = strlen(s);
1822     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1823     if (outCount < count) {
1824         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1825     }
1826 }
1827
1828 /* This is used for sending logon scripts to the ICS. Sending
1829    without a delay causes problems when using timestamp on ICC
1830    (at least on my machine). */
1831 void
1832 SendToICSDelayed(s,msdelay)
1833      char *s;
1834      long msdelay;
1835 {
1836     int count, outCount, outError;
1837
1838     if (icsPR == NULL) return;
1839
1840     count = strlen(s);
1841     if (appData.debugMode) {
1842         fprintf(debugFP, ">ICS: ");
1843         show_bytes(debugFP, s, count);
1844         fprintf(debugFP, "\n");
1845     }
1846     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1847                                       msdelay);
1848     if (outCount < count) {
1849         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1850     }
1851 }
1852
1853
1854 /* Remove all highlighting escape sequences in s
1855    Also deletes any suffix starting with '('
1856    */
1857 char *
1858 StripHighlightAndTitle(s)
1859      char *s;
1860 {
1861     static char retbuf[MSG_SIZ];
1862     char *p = retbuf;
1863
1864     while (*s != NULLCHAR) {
1865         while (*s == '\033') {
1866             while (*s != NULLCHAR && !isalpha(*s)) s++;
1867             if (*s != NULLCHAR) s++;
1868         }
1869         while (*s != NULLCHAR && *s != '\033') {
1870             if (*s == '(' || *s == '[') {
1871                 *p = NULLCHAR;
1872                 return retbuf;
1873             }
1874             *p++ = *s++;
1875         }
1876     }
1877     *p = NULLCHAR;
1878     return retbuf;
1879 }
1880
1881 /* Remove all highlighting escape sequences in s */
1882 char *
1883 StripHighlight(s)
1884      char *s;
1885 {
1886     static char retbuf[MSG_SIZ];
1887     char *p = retbuf;
1888
1889     while (*s != NULLCHAR) {
1890         while (*s == '\033') {
1891             while (*s != NULLCHAR && !isalpha(*s)) s++;
1892             if (*s != NULLCHAR) s++;
1893         }
1894         while (*s != NULLCHAR && *s != '\033') {
1895             *p++ = *s++;
1896         }
1897     }
1898     *p = NULLCHAR;
1899     return retbuf;
1900 }
1901
1902 char *variantNames[] = VARIANT_NAMES;
1903 char *
1904 VariantName(v)
1905      VariantClass v;
1906 {
1907     return variantNames[v];
1908 }
1909
1910
1911 /* Identify a variant from the strings the chess servers use or the
1912    PGN Variant tag names we use. */
1913 VariantClass
1914 StringToVariant(e)
1915      char *e;
1916 {
1917     char *p;
1918     int wnum = -1;
1919     VariantClass v = VariantNormal;
1920     int i, found = FALSE;
1921     char buf[MSG_SIZ];
1922     int len;
1923
1924     if (!e) return v;
1925
1926     /* [HGM] skip over optional board-size prefixes */
1927     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1928         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1929         while( *e++ != '_');
1930     }
1931
1932     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1933         v = VariantNormal;
1934         found = TRUE;
1935     } else
1936     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1937       if (StrCaseStr(e, variantNames[i])) {
1938         v = (VariantClass) i;
1939         found = TRUE;
1940         break;
1941       }
1942     }
1943
1944     if (!found) {
1945       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1946           || StrCaseStr(e, "wild/fr")
1947           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1948         v = VariantFischeRandom;
1949       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1950                  (i = 1, p = StrCaseStr(e, "w"))) {
1951         p += i;
1952         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1953         if (isdigit(*p)) {
1954           wnum = atoi(p);
1955         } else {
1956           wnum = -1;
1957         }
1958         switch (wnum) {
1959         case 0: /* FICS only, actually */
1960         case 1:
1961           /* Castling legal even if K starts on d-file */
1962           v = VariantWildCastle;
1963           break;
1964         case 2:
1965         case 3:
1966         case 4:
1967           /* Castling illegal even if K & R happen to start in
1968              normal positions. */
1969           v = VariantNoCastle;
1970           break;
1971         case 5:
1972         case 7:
1973         case 8:
1974         case 10:
1975         case 11:
1976         case 12:
1977         case 13:
1978         case 14:
1979         case 15:
1980         case 18:
1981         case 19:
1982           /* Castling legal iff K & R start in normal positions */
1983           v = VariantNormal;
1984           break;
1985         case 6:
1986         case 20:
1987         case 21:
1988           /* Special wilds for position setup; unclear what to do here */
1989           v = VariantLoadable;
1990           break;
1991         case 9:
1992           /* Bizarre ICC game */
1993           v = VariantTwoKings;
1994           break;
1995         case 16:
1996           v = VariantKriegspiel;
1997           break;
1998         case 17:
1999           v = VariantLosers;
2000           break;
2001         case 22:
2002           v = VariantFischeRandom;
2003           break;
2004         case 23:
2005           v = VariantCrazyhouse;
2006           break;
2007         case 24:
2008           v = VariantBughouse;
2009           break;
2010         case 25:
2011           v = Variant3Check;
2012           break;
2013         case 26:
2014           /* Not quite the same as FICS suicide! */
2015           v = VariantGiveaway;
2016           break;
2017         case 27:
2018           v = VariantAtomic;
2019           break;
2020         case 28:
2021           v = VariantShatranj;
2022           break;
2023
2024         /* Temporary names for future ICC types.  The name *will* change in
2025            the next xboard/WinBoard release after ICC defines it. */
2026         case 29:
2027           v = Variant29;
2028           break;
2029         case 30:
2030           v = Variant30;
2031           break;
2032         case 31:
2033           v = Variant31;
2034           break;
2035         case 32:
2036           v = Variant32;
2037           break;
2038         case 33:
2039           v = Variant33;
2040           break;
2041         case 34:
2042           v = Variant34;
2043           break;
2044         case 35:
2045           v = Variant35;
2046           break;
2047         case 36:
2048           v = Variant36;
2049           break;
2050         case 37:
2051           v = VariantShogi;
2052           break;
2053         case 38:
2054           v = VariantXiangqi;
2055           break;
2056         case 39:
2057           v = VariantCourier;
2058           break;
2059         case 40:
2060           v = VariantGothic;
2061           break;
2062         case 41:
2063           v = VariantCapablanca;
2064           break;
2065         case 42:
2066           v = VariantKnightmate;
2067           break;
2068         case 43:
2069           v = VariantFairy;
2070           break;
2071         case 44:
2072           v = VariantCylinder;
2073           break;
2074         case 45:
2075           v = VariantFalcon;
2076           break;
2077         case 46:
2078           v = VariantCapaRandom;
2079           break;
2080         case 47:
2081           v = VariantBerolina;
2082           break;
2083         case 48:
2084           v = VariantJanus;
2085           break;
2086         case 49:
2087           v = VariantSuper;
2088           break;
2089         case 50:
2090           v = VariantGreat;
2091           break;
2092         case -1:
2093           /* Found "wild" or "w" in the string but no number;
2094              must assume it's normal chess. */
2095           v = VariantNormal;
2096           break;
2097         default:
2098           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2099           if( (len > MSG_SIZ) && appData.debugMode )
2100             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2101
2102           DisplayError(buf, 0);
2103           v = VariantUnknown;
2104           break;
2105         }
2106       }
2107     }
2108     if (appData.debugMode) {
2109       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2110               e, wnum, VariantName(v));
2111     }
2112     return v;
2113 }
2114
2115 static int leftover_start = 0, leftover_len = 0;
2116 char star_match[STAR_MATCH_N][MSG_SIZ];
2117
2118 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2119    advance *index beyond it, and set leftover_start to the new value of
2120    *index; else return FALSE.  If pattern contains the character '*', it
2121    matches any sequence of characters not containing '\r', '\n', or the
2122    character following the '*' (if any), and the matched sequence(s) are
2123    copied into star_match.
2124    */
2125 int
2126 looking_at(buf, index, pattern)
2127      char *buf;
2128      int *index;
2129      char *pattern;
2130 {
2131     char *bufp = &buf[*index], *patternp = pattern;
2132     int star_count = 0;
2133     char *matchp = star_match[0];
2134
2135     for (;;) {
2136         if (*patternp == NULLCHAR) {
2137             *index = leftover_start = bufp - buf;
2138             *matchp = NULLCHAR;
2139             return TRUE;
2140         }
2141         if (*bufp == NULLCHAR) return FALSE;
2142         if (*patternp == '*') {
2143             if (*bufp == *(patternp + 1)) {
2144                 *matchp = NULLCHAR;
2145                 matchp = star_match[++star_count];
2146                 patternp += 2;
2147                 bufp++;
2148                 continue;
2149             } else if (*bufp == '\n' || *bufp == '\r') {
2150                 patternp++;
2151                 if (*patternp == NULLCHAR)
2152                   continue;
2153                 else
2154                   return FALSE;
2155             } else {
2156                 *matchp++ = *bufp++;
2157                 continue;
2158             }
2159         }
2160         if (*patternp != *bufp) return FALSE;
2161         patternp++;
2162         bufp++;
2163     }
2164 }
2165
2166 void
2167 SendToPlayer(data, length)
2168      char *data;
2169      int length;
2170 {
2171     int error, outCount;
2172     outCount = OutputToProcess(NoProc, data, length, &error);
2173     if (outCount < length) {
2174         DisplayFatalError(_("Error writing to display"), error, 1);
2175     }
2176 }
2177
2178 void
2179 PackHolding(packed, holding)
2180      char packed[];
2181      char *holding;
2182 {
2183     char *p = holding;
2184     char *q = packed;
2185     int runlength = 0;
2186     int curr = 9999;
2187     do {
2188         if (*p == curr) {
2189             runlength++;
2190         } else {
2191             switch (runlength) {
2192               case 0:
2193                 break;
2194               case 1:
2195                 *q++ = curr;
2196                 break;
2197               case 2:
2198                 *q++ = curr;
2199                 *q++ = curr;
2200                 break;
2201               default:
2202                 sprintf(q, "%d", runlength);
2203                 while (*q) q++;
2204                 *q++ = curr;
2205                 break;
2206             }
2207             runlength = 1;
2208             curr = *p;
2209         }
2210     } while (*p++);
2211     *q = NULLCHAR;
2212 }
2213
2214 /* Telnet protocol requests from the front end */
2215 void
2216 TelnetRequest(ddww, option)
2217      unsigned char ddww, option;
2218 {
2219     unsigned char msg[3];
2220     int outCount, outError;
2221
2222     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2223
2224     if (appData.debugMode) {
2225         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2226         switch (ddww) {
2227           case TN_DO:
2228             ddwwStr = "DO";
2229             break;
2230           case TN_DONT:
2231             ddwwStr = "DONT";
2232             break;
2233           case TN_WILL:
2234             ddwwStr = "WILL";
2235             break;
2236           case TN_WONT:
2237             ddwwStr = "WONT";
2238             break;
2239           default:
2240             ddwwStr = buf1;
2241             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2242             break;
2243         }
2244         switch (option) {
2245           case TN_ECHO:
2246             optionStr = "ECHO";
2247             break;
2248           default:
2249             optionStr = buf2;
2250             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2251             break;
2252         }
2253         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2254     }
2255     msg[0] = TN_IAC;
2256     msg[1] = ddww;
2257     msg[2] = option;
2258     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2259     if (outCount < 3) {
2260         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2261     }
2262 }
2263
2264 void
2265 DoEcho()
2266 {
2267     if (!appData.icsActive) return;
2268     TelnetRequest(TN_DO, TN_ECHO);
2269 }
2270
2271 void
2272 DontEcho()
2273 {
2274     if (!appData.icsActive) return;
2275     TelnetRequest(TN_DONT, TN_ECHO);
2276 }
2277
2278 void
2279 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2280 {
2281     /* put the holdings sent to us by the server on the board holdings area */
2282     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2283     char p;
2284     ChessSquare piece;
2285
2286     if(gameInfo.holdingsWidth < 2)  return;
2287     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2288         return; // prevent overwriting by pre-board holdings
2289
2290     if( (int)lowestPiece >= BlackPawn ) {
2291         holdingsColumn = 0;
2292         countsColumn = 1;
2293         holdingsStartRow = BOARD_HEIGHT-1;
2294         direction = -1;
2295     } else {
2296         holdingsColumn = BOARD_WIDTH-1;
2297         countsColumn = BOARD_WIDTH-2;
2298         holdingsStartRow = 0;
2299         direction = 1;
2300     }
2301
2302     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2303         board[i][holdingsColumn] = EmptySquare;
2304         board[i][countsColumn]   = (ChessSquare) 0;
2305     }
2306     while( (p=*holdings++) != NULLCHAR ) {
2307         piece = CharToPiece( ToUpper(p) );
2308         if(piece == EmptySquare) continue;
2309         /*j = (int) piece - (int) WhitePawn;*/
2310         j = PieceToNumber(piece);
2311         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2312         if(j < 0) continue;               /* should not happen */
2313         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2314         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2315         board[holdingsStartRow+j*direction][countsColumn]++;
2316     }
2317 }
2318
2319
2320 void
2321 VariantSwitch(Board board, VariantClass newVariant)
2322 {
2323    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2324    static Board oldBoard;
2325
2326    startedFromPositionFile = FALSE;
2327    if(gameInfo.variant == newVariant) return;
2328
2329    /* [HGM] This routine is called each time an assignment is made to
2330     * gameInfo.variant during a game, to make sure the board sizes
2331     * are set to match the new variant. If that means adding or deleting
2332     * holdings, we shift the playing board accordingly
2333     * This kludge is needed because in ICS observe mode, we get boards
2334     * of an ongoing game without knowing the variant, and learn about the
2335     * latter only later. This can be because of the move list we requested,
2336     * in which case the game history is refilled from the beginning anyway,
2337     * but also when receiving holdings of a crazyhouse game. In the latter
2338     * case we want to add those holdings to the already received position.
2339     */
2340
2341
2342    if (appData.debugMode) {
2343      fprintf(debugFP, "Switch board from %s to %s\n",
2344              VariantName(gameInfo.variant), VariantName(newVariant));
2345      setbuf(debugFP, NULL);
2346    }
2347    shuffleOpenings = 0;       /* [HGM] shuffle */
2348    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2349    switch(newVariant)
2350      {
2351      case VariantShogi:
2352        newWidth = 9;  newHeight = 9;
2353        gameInfo.holdingsSize = 7;
2354      case VariantBughouse:
2355      case VariantCrazyhouse:
2356        newHoldingsWidth = 2; break;
2357      case VariantGreat:
2358        newWidth = 10;
2359      case VariantSuper:
2360        newHoldingsWidth = 2;
2361        gameInfo.holdingsSize = 8;
2362        break;
2363      case VariantGothic:
2364      case VariantCapablanca:
2365      case VariantCapaRandom:
2366        newWidth = 10;
2367      default:
2368        newHoldingsWidth = gameInfo.holdingsSize = 0;
2369      };
2370
2371    if(newWidth  != gameInfo.boardWidth  ||
2372       newHeight != gameInfo.boardHeight ||
2373       newHoldingsWidth != gameInfo.holdingsWidth ) {
2374
2375      /* shift position to new playing area, if needed */
2376      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2377        for(i=0; i<BOARD_HEIGHT; i++)
2378          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2379            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2380              board[i][j];
2381        for(i=0; i<newHeight; i++) {
2382          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2383          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2384        }
2385      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2386        for(i=0; i<BOARD_HEIGHT; i++)
2387          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2388            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2389              board[i][j];
2390      }
2391      gameInfo.boardWidth  = newWidth;
2392      gameInfo.boardHeight = newHeight;
2393      gameInfo.holdingsWidth = newHoldingsWidth;
2394      gameInfo.variant = newVariant;
2395      InitDrawingSizes(-2, 0);
2396    } else gameInfo.variant = newVariant;
2397    CopyBoard(oldBoard, board);   // remember correctly formatted board
2398      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2399    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2400 }
2401
2402 static int loggedOn = FALSE;
2403
2404 /*-- Game start info cache: --*/
2405 int gs_gamenum;
2406 char gs_kind[MSG_SIZ];
2407 static char player1Name[128] = "";
2408 static char player2Name[128] = "";
2409 static char cont_seq[] = "\n\\   ";
2410 static int player1Rating = -1;
2411 static int player2Rating = -1;
2412 /*----------------------------*/
2413
2414 ColorClass curColor = ColorNormal;
2415 int suppressKibitz = 0;
2416
2417 // [HGM] seekgraph
2418 Boolean soughtPending = FALSE;
2419 Boolean seekGraphUp;
2420 #define MAX_SEEK_ADS 200
2421 #define SQUARE 0x80
2422 char *seekAdList[MAX_SEEK_ADS];
2423 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2424 float tcList[MAX_SEEK_ADS];
2425 char colorList[MAX_SEEK_ADS];
2426 int nrOfSeekAds = 0;
2427 int minRating = 1010, maxRating = 2800;
2428 int hMargin = 10, vMargin = 20, h, w;
2429 extern int squareSize, lineGap;
2430
2431 void
2432 PlotSeekAd(int i)
2433 {
2434         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2435         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2436         if(r < minRating+100 && r >=0 ) r = minRating+100;
2437         if(r > maxRating) r = maxRating;
2438         if(tc < 1.) tc = 1.;
2439         if(tc > 95.) tc = 95.;
2440         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2441         y = ((double)r - minRating)/(maxRating - minRating)
2442             * (h-vMargin-squareSize/8-1) + vMargin;
2443         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2444         if(strstr(seekAdList[i], " u ")) color = 1;
2445         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2446            !strstr(seekAdList[i], "bullet") &&
2447            !strstr(seekAdList[i], "blitz") &&
2448            !strstr(seekAdList[i], "standard") ) color = 2;
2449         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2450         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2451 }
2452
2453 void
2454 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2455 {
2456         char buf[MSG_SIZ], *ext = "";
2457         VariantClass v = StringToVariant(type);
2458         if(strstr(type, "wild")) {
2459             ext = type + 4; // append wild number
2460             if(v == VariantFischeRandom) type = "chess960"; else
2461             if(v == VariantLoadable) type = "setup"; else
2462             type = VariantName(v);
2463         }
2464         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2465         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2466             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2467             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2468             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2469             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2470             seekNrList[nrOfSeekAds] = nr;
2471             zList[nrOfSeekAds] = 0;
2472             seekAdList[nrOfSeekAds++] = StrSave(buf);
2473             if(plot) PlotSeekAd(nrOfSeekAds-1);
2474         }
2475 }
2476
2477 void
2478 EraseSeekDot(int i)
2479 {
2480     int x = xList[i], y = yList[i], d=squareSize/4, k;
2481     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2482     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2483     // now replot every dot that overlapped
2484     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2485         int xx = xList[k], yy = yList[k];
2486         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2487             DrawSeekDot(xx, yy, colorList[k]);
2488     }
2489 }
2490
2491 void
2492 RemoveSeekAd(int nr)
2493 {
2494         int i;
2495         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2496             EraseSeekDot(i);
2497             if(seekAdList[i]) free(seekAdList[i]);
2498             seekAdList[i] = seekAdList[--nrOfSeekAds];
2499             seekNrList[i] = seekNrList[nrOfSeekAds];
2500             ratingList[i] = ratingList[nrOfSeekAds];
2501             colorList[i]  = colorList[nrOfSeekAds];
2502             tcList[i] = tcList[nrOfSeekAds];
2503             xList[i]  = xList[nrOfSeekAds];
2504             yList[i]  = yList[nrOfSeekAds];
2505             zList[i]  = zList[nrOfSeekAds];
2506             seekAdList[nrOfSeekAds] = NULL;
2507             break;
2508         }
2509 }
2510
2511 Boolean
2512 MatchSoughtLine(char *line)
2513 {
2514     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2515     int nr, base, inc, u=0; char dummy;
2516
2517     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2518        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2519        (u=1) &&
2520        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2521         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2522         // match: compact and save the line
2523         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2524         return TRUE;
2525     }
2526     return FALSE;
2527 }
2528
2529 int
2530 DrawSeekGraph()
2531 {
2532     int i;
2533     if(!seekGraphUp) return FALSE;
2534     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2535     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2536
2537     DrawSeekBackground(0, 0, w, h);
2538     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2539     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2540     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2541         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2542         yy = h-1-yy;
2543         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2544         if(i%500 == 0) {
2545             char buf[MSG_SIZ];
2546             snprintf(buf, MSG_SIZ, "%d", i);
2547             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2548         }
2549     }
2550     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2551     for(i=1; i<100; i+=(i<10?1:5)) {
2552         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2553         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2554         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2555             char buf[MSG_SIZ];
2556             snprintf(buf, MSG_SIZ, "%d", i);
2557             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2558         }
2559     }
2560     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2561     return TRUE;
2562 }
2563
2564 int SeekGraphClick(ClickType click, int x, int y, int moving)
2565 {
2566     static int lastDown = 0, displayed = 0, lastSecond;
2567     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2568         if(click == Release || moving) return FALSE;
2569         nrOfSeekAds = 0;
2570         soughtPending = TRUE;
2571         SendToICS(ics_prefix);
2572         SendToICS("sought\n"); // should this be "sought all"?
2573     } else { // issue challenge based on clicked ad
2574         int dist = 10000; int i, closest = 0, second = 0;
2575         for(i=0; i<nrOfSeekAds; i++) {
2576             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2577             if(d < dist) { dist = d; closest = i; }
2578             second += (d - zList[i] < 120); // count in-range ads
2579             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2580         }
2581         if(dist < 120) {
2582             char buf[MSG_SIZ];
2583             second = (second > 1);
2584             if(displayed != closest || second != lastSecond) {
2585                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2586                 lastSecond = second; displayed = closest;
2587             }
2588             if(click == Press) {
2589                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2590                 lastDown = closest;
2591                 return TRUE;
2592             } // on press 'hit', only show info
2593             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2594             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2595             SendToICS(ics_prefix);
2596             SendToICS(buf);
2597             return TRUE; // let incoming board of started game pop down the graph
2598         } else if(click == Release) { // release 'miss' is ignored
2599             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2600             if(moving == 2) { // right up-click
2601                 nrOfSeekAds = 0; // refresh graph
2602                 soughtPending = TRUE;
2603                 SendToICS(ics_prefix);
2604                 SendToICS("sought\n"); // should this be "sought all"?
2605             }
2606             return TRUE;
2607         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2608         // press miss or release hit 'pop down' seek graph
2609         seekGraphUp = FALSE;
2610         DrawPosition(TRUE, NULL);
2611     }
2612     return TRUE;
2613 }
2614
2615 void
2616 read_from_ics(isr, closure, data, count, error)
2617      InputSourceRef isr;
2618      VOIDSTAR closure;
2619      char *data;
2620      int count;
2621      int error;
2622 {
2623 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2624 #define STARTED_NONE 0
2625 #define STARTED_MOVES 1
2626 #define STARTED_BOARD 2
2627 #define STARTED_OBSERVE 3
2628 #define STARTED_HOLDINGS 4
2629 #define STARTED_CHATTER 5
2630 #define STARTED_COMMENT 6
2631 #define STARTED_MOVES_NOHIDE 7
2632
2633     static int started = STARTED_NONE;
2634     static char parse[20000];
2635     static int parse_pos = 0;
2636     static char buf[BUF_SIZE + 1];
2637     static int firstTime = TRUE, intfSet = FALSE;
2638     static ColorClass prevColor = ColorNormal;
2639     static int savingComment = FALSE;
2640     static int cmatch = 0; // continuation sequence match
2641     char *bp;
2642     char str[MSG_SIZ];
2643     int i, oldi;
2644     int buf_len;
2645     int next_out;
2646     int tkind;
2647     int backup;    /* [DM] For zippy color lines */
2648     char *p;
2649     char talker[MSG_SIZ]; // [HGM] chat
2650     int channel;
2651
2652     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2653
2654     if (appData.debugMode) {
2655       if (!error) {
2656         fprintf(debugFP, "<ICS: ");
2657         show_bytes(debugFP, data, count);
2658         fprintf(debugFP, "\n");
2659       }
2660     }
2661
2662     if (appData.debugMode) { int f = forwardMostMove;
2663         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2664                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2665                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2666     }
2667     if (count > 0) {
2668         /* If last read ended with a partial line that we couldn't parse,
2669            prepend it to the new read and try again. */
2670         if (leftover_len > 0) {
2671             for (i=0; i<leftover_len; i++)
2672               buf[i] = buf[leftover_start + i];
2673         }
2674
2675     /* copy new characters into the buffer */
2676     bp = buf + leftover_len;
2677     buf_len=leftover_len;
2678     for (i=0; i<count; i++)
2679     {
2680         // ignore these
2681         if (data[i] == '\r')
2682             continue;
2683
2684         // join lines split by ICS?
2685         if (!appData.noJoin)
2686         {
2687             /*
2688                 Joining just consists of finding matches against the
2689                 continuation sequence, and discarding that sequence
2690                 if found instead of copying it.  So, until a match
2691                 fails, there's nothing to do since it might be the
2692                 complete sequence, and thus, something we don't want
2693                 copied.
2694             */
2695             if (data[i] == cont_seq[cmatch])
2696             {
2697                 cmatch++;
2698                 if (cmatch == strlen(cont_seq))
2699                 {
2700                     cmatch = 0; // complete match.  just reset the counter
2701
2702                     /*
2703                         it's possible for the ICS to not include the space
2704                         at the end of the last word, making our [correct]
2705                         join operation fuse two separate words.  the server
2706                         does this when the space occurs at the width setting.
2707                     */
2708                     if (!buf_len || buf[buf_len-1] != ' ')
2709                     {
2710                         *bp++ = ' ';
2711                         buf_len++;
2712                     }
2713                 }
2714                 continue;
2715             }
2716             else if (cmatch)
2717             {
2718                 /*
2719                     match failed, so we have to copy what matched before
2720                     falling through and copying this character.  In reality,
2721                     this will only ever be just the newline character, but
2722                     it doesn't hurt to be precise.
2723                 */
2724                 strncpy(bp, cont_seq, cmatch);
2725                 bp += cmatch;
2726                 buf_len += cmatch;
2727                 cmatch = 0;
2728             }
2729         }
2730
2731         // copy this char
2732         *bp++ = data[i];
2733         buf_len++;
2734     }
2735
2736         buf[buf_len] = NULLCHAR;
2737 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2738         next_out = 0;
2739         leftover_start = 0;
2740
2741         i = 0;
2742         while (i < buf_len) {
2743             /* Deal with part of the TELNET option negotiation
2744                protocol.  We refuse to do anything beyond the
2745                defaults, except that we allow the WILL ECHO option,
2746                which ICS uses to turn off password echoing when we are
2747                directly connected to it.  We reject this option
2748                if localLineEditing mode is on (always on in xboard)
2749                and we are talking to port 23, which might be a real
2750                telnet server that will try to keep WILL ECHO on permanently.
2751              */
2752             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2753                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2754                 unsigned char option;
2755                 oldi = i;
2756                 switch ((unsigned char) buf[++i]) {
2757                   case TN_WILL:
2758                     if (appData.debugMode)
2759                       fprintf(debugFP, "\n<WILL ");
2760                     switch (option = (unsigned char) buf[++i]) {
2761                       case TN_ECHO:
2762                         if (appData.debugMode)
2763                           fprintf(debugFP, "ECHO ");
2764                         /* Reply only if this is a change, according
2765                            to the protocol rules. */
2766                         if (remoteEchoOption) break;
2767                         if (appData.localLineEditing &&
2768                             atoi(appData.icsPort) == TN_PORT) {
2769                             TelnetRequest(TN_DONT, TN_ECHO);
2770                         } else {
2771                             EchoOff();
2772                             TelnetRequest(TN_DO, TN_ECHO);
2773                             remoteEchoOption = TRUE;
2774                         }
2775                         break;
2776                       default:
2777                         if (appData.debugMode)
2778                           fprintf(debugFP, "%d ", option);
2779                         /* Whatever this is, we don't want it. */
2780                         TelnetRequest(TN_DONT, option);
2781                         break;
2782                     }
2783                     break;
2784                   case TN_WONT:
2785                     if (appData.debugMode)
2786                       fprintf(debugFP, "\n<WONT ");
2787                     switch (option = (unsigned char) buf[++i]) {
2788                       case TN_ECHO:
2789                         if (appData.debugMode)
2790                           fprintf(debugFP, "ECHO ");
2791                         /* Reply only if this is a change, according
2792                            to the protocol rules. */
2793                         if (!remoteEchoOption) break;
2794                         EchoOn();
2795                         TelnetRequest(TN_DONT, TN_ECHO);
2796                         remoteEchoOption = FALSE;
2797                         break;
2798                       default:
2799                         if (appData.debugMode)
2800                           fprintf(debugFP, "%d ", (unsigned char) option);
2801                         /* Whatever this is, it must already be turned
2802                            off, because we never agree to turn on
2803                            anything non-default, so according to the
2804                            protocol rules, we don't reply. */
2805                         break;
2806                     }
2807                     break;
2808                   case TN_DO:
2809                     if (appData.debugMode)
2810                       fprintf(debugFP, "\n<DO ");
2811                     switch (option = (unsigned char) buf[++i]) {
2812                       default:
2813                         /* Whatever this is, we refuse to do it. */
2814                         if (appData.debugMode)
2815                           fprintf(debugFP, "%d ", option);
2816                         TelnetRequest(TN_WONT, option);
2817                         break;
2818                     }
2819                     break;
2820                   case TN_DONT:
2821                     if (appData.debugMode)
2822                       fprintf(debugFP, "\n<DONT ");
2823                     switch (option = (unsigned char) buf[++i]) {
2824                       default:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "%d ", option);
2827                         /* Whatever this is, we are already not doing
2828                            it, because we never agree to do anything
2829                            non-default, so according to the protocol
2830                            rules, we don't reply. */
2831                         break;
2832                     }
2833                     break;
2834                   case TN_IAC:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<IAC ");
2837                     /* Doubled IAC; pass it through */
2838                     i--;
2839                     break;
2840                   default:
2841                     if (appData.debugMode)
2842                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2843                     /* Drop all other telnet commands on the floor */
2844                     break;
2845                 }
2846                 if (oldi > next_out)
2847                   SendToPlayer(&buf[next_out], oldi - next_out);
2848                 if (++i > next_out)
2849                   next_out = i;
2850                 continue;
2851             }
2852
2853             /* OK, this at least will *usually* work */
2854             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2855                 loggedOn = TRUE;
2856             }
2857
2858             if (loggedOn && !intfSet) {
2859                 if (ics_type == ICS_ICC) {
2860                   snprintf(str, MSG_SIZ,
2861                           "/set-quietly interface %s\n/set-quietly style 12\n",
2862                           programVersion);
2863                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2865                 } else if (ics_type == ICS_CHESSNET) {
2866                   snprintf(str, MSG_SIZ, "/style 12\n");
2867                 } else {
2868                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2869                   strcat(str, programVersion);
2870                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2871                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2872                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2873 #ifdef WIN32
2874                   strcat(str, "$iset nohighlight 1\n");
2875 #endif
2876                   strcat(str, "$iset lock 1\n$style 12\n");
2877                 }
2878                 SendToICS(str);
2879                 NotifyFrontendLogin();
2880                 intfSet = TRUE;
2881             }
2882
2883             if (started == STARTED_COMMENT) {
2884                 /* Accumulate characters in comment */
2885                 parse[parse_pos++] = buf[i];
2886                 if (buf[i] == '\n') {
2887                     parse[parse_pos] = NULLCHAR;
2888                     if(chattingPartner>=0) {
2889                         char mess[MSG_SIZ];
2890                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2891                         OutputChatMessage(chattingPartner, mess);
2892                         chattingPartner = -1;
2893                         next_out = i+1; // [HGM] suppress printing in ICS window
2894                     } else
2895                     if(!suppressKibitz) // [HGM] kibitz
2896                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2897                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2898                         int nrDigit = 0, nrAlph = 0, j;
2899                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2900                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2901                         parse[parse_pos] = NULLCHAR;
2902                         // try to be smart: if it does not look like search info, it should go to
2903                         // ICS interaction window after all, not to engine-output window.
2904                         for(j=0; j<parse_pos; j++) { // count letters and digits
2905                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2906                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2907                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2908                         }
2909                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2910                             int depth=0; float score;
2911                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2912                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2913                                 pvInfoList[forwardMostMove-1].depth = depth;
2914                                 pvInfoList[forwardMostMove-1].score = 100*score;
2915                             }
2916                             OutputKibitz(suppressKibitz, parse);
2917                         } else {
2918                             char tmp[MSG_SIZ];
2919                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2920                             SendToPlayer(tmp, strlen(tmp));
2921                         }
2922                         next_out = i+1; // [HGM] suppress printing in ICS window
2923                     }
2924                     started = STARTED_NONE;
2925                 } else {
2926                     /* Don't match patterns against characters in comment */
2927                     i++;
2928                     continue;
2929                 }
2930             }
2931             if (started == STARTED_CHATTER) {
2932                 if (buf[i] != '\n') {
2933                     /* Don't match patterns against characters in chatter */
2934                     i++;
2935                     continue;
2936                 }
2937                 started = STARTED_NONE;
2938                 if(suppressKibitz) next_out = i+1;
2939             }
2940
2941             /* Kludge to deal with rcmd protocol */
2942             if (firstTime && looking_at(buf, &i, "\001*")) {
2943                 DisplayFatalError(&buf[1], 0, 1);
2944                 continue;
2945             } else {
2946                 firstTime = FALSE;
2947             }
2948
2949             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2950                 ics_type = ICS_ICC;
2951                 ics_prefix = "/";
2952                 if (appData.debugMode)
2953                   fprintf(debugFP, "ics_type %d\n", ics_type);
2954                 continue;
2955             }
2956             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2957                 ics_type = ICS_FICS;
2958                 ics_prefix = "$";
2959                 if (appData.debugMode)
2960                   fprintf(debugFP, "ics_type %d\n", ics_type);
2961                 continue;
2962             }
2963             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2964                 ics_type = ICS_CHESSNET;
2965                 ics_prefix = "/";
2966                 if (appData.debugMode)
2967                   fprintf(debugFP, "ics_type %d\n", ics_type);
2968                 continue;
2969             }
2970
2971             if (!loggedOn &&
2972                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2973                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2974                  looking_at(buf, &i, "will be \"*\""))) {
2975               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2976               continue;
2977             }
2978
2979             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2980               char buf[MSG_SIZ];
2981               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2982               DisplayIcsInteractionTitle(buf);
2983               have_set_title = TRUE;
2984             }
2985
2986             /* skip finger notes */
2987             if (started == STARTED_NONE &&
2988                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2989                  (buf[i] == '1' && buf[i+1] == '0')) &&
2990                 buf[i+2] == ':' && buf[i+3] == ' ') {
2991               started = STARTED_CHATTER;
2992               i += 3;
2993               continue;
2994             }
2995
2996             oldi = i;
2997             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2998             if(appData.seekGraph) {
2999                 if(soughtPending && MatchSoughtLine(buf+i)) {
3000                     i = strstr(buf+i, "rated") - buf;
3001                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3002                     next_out = leftover_start = i;
3003                     started = STARTED_CHATTER;
3004                     suppressKibitz = TRUE;
3005                     continue;
3006                 }
3007                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3008                         && looking_at(buf, &i, "* ads displayed")) {
3009                     soughtPending = FALSE;
3010                     seekGraphUp = TRUE;
3011                     DrawSeekGraph();
3012                     continue;
3013                 }
3014                 if(appData.autoRefresh) {
3015                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3016                         int s = (ics_type == ICS_ICC); // ICC format differs
3017                         if(seekGraphUp)
3018                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3019                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3020                         looking_at(buf, &i, "*% "); // eat prompt
3021                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3022                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3023                         next_out = i; // suppress
3024                         continue;
3025                     }
3026                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3027                         char *p = star_match[0];
3028                         while(*p) {
3029                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3030                             while(*p && *p++ != ' '); // next
3031                         }
3032                         looking_at(buf, &i, "*% "); // eat prompt
3033                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3034                         next_out = i;
3035                         continue;
3036                     }
3037                 }
3038             }
3039
3040             /* skip formula vars */
3041             if (started == STARTED_NONE &&
3042                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3043               started = STARTED_CHATTER;
3044               i += 3;
3045               continue;
3046             }
3047
3048             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3049             if (appData.autoKibitz && started == STARTED_NONE &&
3050                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3051                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3052                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3053                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3054                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3055                         suppressKibitz = TRUE;
3056                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3057                         next_out = i;
3058                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3059                                 && (gameMode == IcsPlayingWhite)) ||
3060                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3061                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3062                             started = STARTED_CHATTER; // own kibitz we simply discard
3063                         else {
3064                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3065                             parse_pos = 0; parse[0] = NULLCHAR;
3066                             savingComment = TRUE;
3067                             suppressKibitz = gameMode != IcsObserving ? 2 :
3068                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3069                         }
3070                         continue;
3071                 } else
3072                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3073                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3074                          && atoi(star_match[0])) {
3075                     // suppress the acknowledgements of our own autoKibitz
3076                     char *p;
3077                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3079                     SendToPlayer(star_match[0], strlen(star_match[0]));
3080                     if(looking_at(buf, &i, "*% ")) // eat prompt
3081                         suppressKibitz = FALSE;
3082                     next_out = i;
3083                     continue;
3084                 }
3085             } // [HGM] kibitz: end of patch
3086
3087             // [HGM] chat: intercept tells by users for which we have an open chat window
3088             channel = -1;
3089             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3090                                            looking_at(buf, &i, "* whispers:") ||
3091                                            looking_at(buf, &i, "* kibitzes:") ||
3092                                            looking_at(buf, &i, "* shouts:") ||
3093                                            looking_at(buf, &i, "* c-shouts:") ||
3094                                            looking_at(buf, &i, "--> * ") ||
3095                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3096                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3097                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3098                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3099                 int p;
3100                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3101                 chattingPartner = -1;
3102
3103                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3104                 for(p=0; p<MAX_CHAT; p++) {
3105                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3106                     talker[0] = '['; strcat(talker, "] ");
3107                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3108                     chattingPartner = p; break;
3109                     }
3110                 } else
3111                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3112                 for(p=0; p<MAX_CHAT; p++) {
3113                     if(!strcmp("kibitzes", chatPartner[p])) {
3114                         talker[0] = '['; strcat(talker, "] ");
3115                         chattingPartner = p; break;
3116                     }
3117                 } else
3118                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3119                 for(p=0; p<MAX_CHAT; p++) {
3120                     if(!strcmp("whispers", chatPartner[p])) {
3121                         talker[0] = '['; strcat(talker, "] ");
3122                         chattingPartner = p; break;
3123                     }
3124                 } else
3125                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3126                   if(buf[i-8] == '-' && buf[i-3] == 't')
3127                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3128                     if(!strcmp("c-shouts", chatPartner[p])) {
3129                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3130                         chattingPartner = p; break;
3131                     }
3132                   }
3133                   if(chattingPartner < 0)
3134                   for(p=0; p<MAX_CHAT; p++) {
3135                     if(!strcmp("shouts", chatPartner[p])) {
3136                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3137                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3138                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3139                         chattingPartner = p; break;
3140                     }
3141                   }
3142                 }
3143                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3144                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3145                     talker[0] = 0; Colorize(ColorTell, FALSE);
3146                     chattingPartner = p; break;
3147                 }
3148                 if(chattingPartner<0) i = oldi; else {
3149                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3150                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3151                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152                     started = STARTED_COMMENT;
3153                     parse_pos = 0; parse[0] = NULLCHAR;
3154                     savingComment = 3 + chattingPartner; // counts as TRUE
3155                     suppressKibitz = TRUE;
3156                     continue;
3157                 }
3158             } // [HGM] chat: end of patch
3159
3160           backup = i;
3161             if (appData.zippyTalk || appData.zippyPlay) {
3162                 /* [DM] Backup address for color zippy lines */
3163 #if ZIPPY
3164                if (loggedOn == TRUE)
3165                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3166                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3167 #endif
3168             } // [DM] 'else { ' deleted
3169                 if (
3170                     /* Regular tells and says */
3171                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3172                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3173                     looking_at(buf, &i, "* says: ") ||
3174                     /* Don't color "message" or "messages" output */
3175                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3176                     looking_at(buf, &i, "*. * at *:*: ") ||
3177                     looking_at(buf, &i, "--* (*:*): ") ||
3178                     /* Message notifications (same color as tells) */
3179                     looking_at(buf, &i, "* has left a message ") ||
3180                     looking_at(buf, &i, "* just sent you a message:\n") ||
3181                     /* Whispers and kibitzes */
3182                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3183                     looking_at(buf, &i, "* kibitzes: ") ||
3184                     /* Channel tells */
3185                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3186
3187                   if (tkind == 1 && strchr(star_match[0], ':')) {
3188                       /* Avoid "tells you:" spoofs in channels */
3189                      tkind = 3;
3190                   }
3191                   if (star_match[0][0] == NULLCHAR ||
3192                       strchr(star_match[0], ' ') ||
3193                       (tkind == 3 && strchr(star_match[1], ' '))) {
3194                     /* Reject bogus matches */
3195                     i = oldi;
3196                   } else {
3197                     if (appData.colorize) {
3198                       if (oldi > next_out) {
3199                         SendToPlayer(&buf[next_out], oldi - next_out);
3200                         next_out = oldi;
3201                       }
3202                       switch (tkind) {
3203                       case 1:
3204                         Colorize(ColorTell, FALSE);
3205                         curColor = ColorTell;
3206                         break;
3207                       case 2:
3208                         Colorize(ColorKibitz, FALSE);
3209                         curColor = ColorKibitz;
3210                         break;
3211                       case 3:
3212                         p = strrchr(star_match[1], '(');
3213                         if (p == NULL) {
3214                           p = star_match[1];
3215                         } else {
3216                           p++;
3217                         }
3218                         if (atoi(p) == 1) {
3219                           Colorize(ColorChannel1, FALSE);
3220                           curColor = ColorChannel1;
3221                         } else {
3222                           Colorize(ColorChannel, FALSE);
3223                           curColor = ColorChannel;
3224                         }
3225                         break;
3226                       case 5:
3227                         curColor = ColorNormal;
3228                         break;
3229                       }
3230                     }
3231                     if (started == STARTED_NONE && appData.autoComment &&
3232                         (gameMode == IcsObserving ||
3233                          gameMode == IcsPlayingWhite ||
3234                          gameMode == IcsPlayingBlack)) {
3235                       parse_pos = i - oldi;
3236                       memcpy(parse, &buf[oldi], parse_pos);
3237                       parse[parse_pos] = NULLCHAR;
3238                       started = STARTED_COMMENT;
3239                       savingComment = TRUE;
3240                     } else {
3241                       started = STARTED_CHATTER;
3242                       savingComment = FALSE;
3243                     }
3244                     loggedOn = TRUE;
3245                     continue;
3246                   }
3247                 }
3248
3249                 if (looking_at(buf, &i, "* s-shouts: ") ||
3250                     looking_at(buf, &i, "* c-shouts: ")) {
3251                     if (appData.colorize) {
3252                         if (oldi > next_out) {
3253                             SendToPlayer(&buf[next_out], oldi - next_out);
3254                             next_out = oldi;
3255                         }
3256                         Colorize(ColorSShout, FALSE);
3257                         curColor = ColorSShout;
3258                     }
3259                     loggedOn = TRUE;
3260                     started = STARTED_CHATTER;
3261                     continue;
3262                 }
3263
3264                 if (looking_at(buf, &i, "--->")) {
3265                     loggedOn = TRUE;
3266                     continue;
3267                 }
3268
3269                 if (looking_at(buf, &i, "* shouts: ") ||
3270                     looking_at(buf, &i, "--> ")) {
3271                     if (appData.colorize) {
3272                         if (oldi > next_out) {
3273                             SendToPlayer(&buf[next_out], oldi - next_out);
3274                             next_out = oldi;
3275                         }
3276                         Colorize(ColorShout, FALSE);
3277                         curColor = ColorShout;
3278                     }
3279                     loggedOn = TRUE;
3280                     started = STARTED_CHATTER;
3281                     continue;
3282                 }
3283
3284                 if (looking_at( buf, &i, "Challenge:")) {
3285                     if (appData.colorize) {
3286                         if (oldi > next_out) {
3287                             SendToPlayer(&buf[next_out], oldi - next_out);
3288                             next_out = oldi;
3289                         }
3290                         Colorize(ColorChallenge, FALSE);
3291                         curColor = ColorChallenge;
3292                     }
3293                     loggedOn = TRUE;
3294                     continue;
3295                 }
3296
3297                 if (looking_at(buf, &i, "* offers you") ||
3298                     looking_at(buf, &i, "* offers to be") ||
3299                     looking_at(buf, &i, "* would like to") ||
3300                     looking_at(buf, &i, "* requests to") ||
3301                     looking_at(buf, &i, "Your opponent offers") ||
3302                     looking_at(buf, &i, "Your opponent requests")) {
3303
3304                     if (appData.colorize) {
3305                         if (oldi > next_out) {
3306                             SendToPlayer(&buf[next_out], oldi - next_out);
3307                             next_out = oldi;
3308                         }
3309                         Colorize(ColorRequest, FALSE);
3310                         curColor = ColorRequest;
3311                     }
3312                     continue;
3313                 }
3314
3315                 if (looking_at(buf, &i, "* (*) seeking")) {
3316                     if (appData.colorize) {
3317                         if (oldi > next_out) {
3318                             SendToPlayer(&buf[next_out], oldi - next_out);
3319                             next_out = oldi;
3320                         }
3321                         Colorize(ColorSeek, FALSE);
3322                         curColor = ColorSeek;
3323                     }
3324                     continue;
3325             }
3326
3327           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3328
3329             if (looking_at(buf, &i, "\\   ")) {
3330                 if (prevColor != ColorNormal) {
3331                     if (oldi > next_out) {
3332                         SendToPlayer(&buf[next_out], oldi - next_out);
3333                         next_out = oldi;
3334                     }
3335                     Colorize(prevColor, TRUE);
3336                     curColor = prevColor;
3337                 }
3338                 if (savingComment) {
3339                     parse_pos = i - oldi;
3340                     memcpy(parse, &buf[oldi], parse_pos);
3341                     parse[parse_pos] = NULLCHAR;
3342                     started = STARTED_COMMENT;
3343                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3344                         chattingPartner = savingComment - 3; // kludge to remember the box
3345                 } else {
3346                     started = STARTED_CHATTER;
3347                 }
3348                 continue;
3349             }
3350
3351             if (looking_at(buf, &i, "Black Strength :") ||
3352                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3353                 looking_at(buf, &i, "<10>") ||
3354                 looking_at(buf, &i, "#@#")) {
3355                 /* Wrong board style */
3356                 loggedOn = TRUE;
3357                 SendToICS(ics_prefix);
3358                 SendToICS("set style 12\n");
3359                 SendToICS(ics_prefix);
3360                 SendToICS("refresh\n");
3361                 continue;
3362             }
3363
3364             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3365                 ICSInitScript();
3366                 have_sent_ICS_logon = 1;
3367                 continue;
3368             }
3369
3370             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3371                 (looking_at(buf, &i, "\n<12> ") ||
3372                  looking_at(buf, &i, "<12> "))) {
3373                 loggedOn = TRUE;
3374                 if (oldi > next_out) {
3375                     SendToPlayer(&buf[next_out], oldi - next_out);
3376                 }
3377                 next_out = i;
3378                 started = STARTED_BOARD;
3379                 parse_pos = 0;
3380                 continue;
3381             }
3382
3383             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3384                 looking_at(buf, &i, "<b1> ")) {
3385                 if (oldi > next_out) {
3386                     SendToPlayer(&buf[next_out], oldi - next_out);
3387                 }
3388                 next_out = i;
3389                 started = STARTED_HOLDINGS;
3390                 parse_pos = 0;
3391                 continue;
3392             }
3393
3394             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3395                 loggedOn = TRUE;
3396                 /* Header for a move list -- first line */
3397
3398                 switch (ics_getting_history) {
3399                   case H_FALSE:
3400                     switch (gameMode) {
3401                       case IcsIdle:
3402                       case BeginningOfGame:
3403                         /* User typed "moves" or "oldmoves" while we
3404                            were idle.  Pretend we asked for these
3405                            moves and soak them up so user can step
3406                            through them and/or save them.
3407                            */
3408                         Reset(FALSE, TRUE);
3409                         gameMode = IcsObserving;
3410                         ModeHighlight();
3411                         ics_gamenum = -1;
3412                         ics_getting_history = H_GOT_UNREQ_HEADER;
3413                         break;
3414                       case EditGame: /*?*/
3415                       case EditPosition: /*?*/
3416                         /* Should above feature work in these modes too? */
3417                         /* For now it doesn't */
3418                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3419                         break;
3420                       default:
3421                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3422                         break;
3423                     }
3424                     break;
3425                   case H_REQUESTED:
3426                     /* Is this the right one? */
3427                     if (gameInfo.white && gameInfo.black &&
3428                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3429                         strcmp(gameInfo.black, star_match[2]) == 0) {
3430                         /* All is well */
3431                         ics_getting_history = H_GOT_REQ_HEADER;
3432                     }
3433                     break;
3434                   case H_GOT_REQ_HEADER:
3435                   case H_GOT_UNREQ_HEADER:
3436                   case H_GOT_UNWANTED_HEADER:
3437                   case H_GETTING_MOVES:
3438                     /* Should not happen */
3439                     DisplayError(_("Error gathering move list: two headers"), 0);
3440                     ics_getting_history = H_FALSE;
3441                     break;
3442                 }
3443
3444                 /* Save player ratings into gameInfo if needed */
3445                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3446                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3447                     (gameInfo.whiteRating == -1 ||
3448                      gameInfo.blackRating == -1)) {
3449
3450                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3451                     gameInfo.blackRating = string_to_rating(star_match[3]);
3452                     if (appData.debugMode)
3453                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3454                               gameInfo.whiteRating, gameInfo.blackRating);
3455                 }
3456                 continue;
3457             }
3458
3459             if (looking_at(buf, &i,
3460               "* * match, initial time: * minute*, increment: * second")) {
3461                 /* Header for a move list -- second line */
3462                 /* Initial board will follow if this is a wild game */
3463                 if (gameInfo.event != NULL) free(gameInfo.event);
3464                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3465                 gameInfo.event = StrSave(str);
3466                 /* [HGM] we switched variant. Translate boards if needed. */
3467                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3468                 continue;
3469             }
3470
3471             if (looking_at(buf, &i, "Move  ")) {
3472                 /* Beginning of a move list */
3473                 switch (ics_getting_history) {
3474                   case H_FALSE:
3475                     /* Normally should not happen */
3476                     /* Maybe user hit reset while we were parsing */
3477                     break;
3478                   case H_REQUESTED:
3479                     /* Happens if we are ignoring a move list that is not
3480                      * the one we just requested.  Common if the user
3481                      * tries to observe two games without turning off
3482                      * getMoveList */
3483                     break;
3484                   case H_GETTING_MOVES:
3485                     /* Should not happen */
3486                     DisplayError(_("Error gathering move list: nested"), 0);
3487                     ics_getting_history = H_FALSE;
3488                     break;
3489                   case H_GOT_REQ_HEADER:
3490                     ics_getting_history = H_GETTING_MOVES;
3491                     started = STARTED_MOVES;
3492                     parse_pos = 0;
3493                     if (oldi > next_out) {
3494                         SendToPlayer(&buf[next_out], oldi - next_out);
3495                     }
3496                     break;
3497                   case H_GOT_UNREQ_HEADER:
3498                     ics_getting_history = H_GETTING_MOVES;
3499                     started = STARTED_MOVES_NOHIDE;
3500                     parse_pos = 0;
3501                     break;
3502                   case H_GOT_UNWANTED_HEADER:
3503                     ics_getting_history = H_FALSE;
3504                     break;
3505                 }
3506                 continue;
3507             }
3508
3509             if (looking_at(buf, &i, "% ") ||
3510                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3511                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3512                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3513                     soughtPending = FALSE;
3514                     seekGraphUp = TRUE;
3515                     DrawSeekGraph();
3516                 }
3517                 if(suppressKibitz) next_out = i;
3518                 savingComment = FALSE;
3519                 suppressKibitz = 0;
3520                 switch (started) {
3521                   case STARTED_MOVES:
3522                   case STARTED_MOVES_NOHIDE:
3523                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3524                     parse[parse_pos + i - oldi] = NULLCHAR;
3525                     ParseGameHistory(parse);
3526 #if ZIPPY
3527                     if (appData.zippyPlay && first.initDone) {
3528                         FeedMovesToProgram(&first, forwardMostMove);
3529                         if (gameMode == IcsPlayingWhite) {
3530                             if (WhiteOnMove(forwardMostMove)) {
3531                                 if (first.sendTime) {
3532                                   if (first.useColors) {
3533                                     SendToProgram("black\n", &first);
3534                                   }
3535                                   SendTimeRemaining(&first, TRUE);
3536                                 }
3537                                 if (first.useColors) {
3538                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3539                                 }
3540                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3541                                 first.maybeThinking = TRUE;
3542                             } else {
3543                                 if (first.usePlayother) {
3544                                   if (first.sendTime) {
3545                                     SendTimeRemaining(&first, TRUE);
3546                                   }
3547                                   SendToProgram("playother\n", &first);
3548                                   firstMove = FALSE;
3549                                 } else {
3550                                   firstMove = TRUE;
3551                                 }
3552                             }
3553                         } else if (gameMode == IcsPlayingBlack) {
3554                             if (!WhiteOnMove(forwardMostMove)) {
3555                                 if (first.sendTime) {
3556                                   if (first.useColors) {
3557                                     SendToProgram("white\n", &first);
3558                                   }
3559                                   SendTimeRemaining(&first, FALSE);
3560                                 }
3561                                 if (first.useColors) {
3562                                   SendToProgram("black\n", &first);
3563                                 }
3564                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3565                                 first.maybeThinking = TRUE;
3566                             } else {
3567                                 if (first.usePlayother) {
3568                                   if (first.sendTime) {
3569                                     SendTimeRemaining(&first, FALSE);
3570                                   }
3571                                   SendToProgram("playother\n", &first);
3572                                   firstMove = FALSE;
3573                                 } else {
3574                                   firstMove = TRUE;
3575                                 }
3576                             }
3577                         }
3578                     }
3579 #endif
3580                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3581                         /* Moves came from oldmoves or moves command
3582                            while we weren't doing anything else.
3583                            */
3584                         currentMove = forwardMostMove;
3585                         ClearHighlights();/*!!could figure this out*/
3586                         flipView = appData.flipView;
3587                         DrawPosition(TRUE, boards[currentMove]);
3588                         DisplayBothClocks();
3589                         snprintf(str, MSG_SIZ, "%s vs. %s",
3590                                 gameInfo.white, gameInfo.black);
3591                         DisplayTitle(str);
3592                         gameMode = IcsIdle;
3593                     } else {
3594                         /* Moves were history of an active game */
3595                         if (gameInfo.resultDetails != NULL) {
3596                             free(gameInfo.resultDetails);
3597                             gameInfo.resultDetails = NULL;
3598                         }
3599                     }
3600                     HistorySet(parseList, backwardMostMove,
3601                                forwardMostMove, currentMove-1);
3602                     DisplayMove(currentMove - 1);
3603                     if (started == STARTED_MOVES) next_out = i;
3604                     started = STARTED_NONE;
3605                     ics_getting_history = H_FALSE;
3606                     break;
3607
3608                   case STARTED_OBSERVE:
3609                     started = STARTED_NONE;
3610                     SendToICS(ics_prefix);
3611                     SendToICS("refresh\n");
3612                     break;
3613
3614                   default:
3615                     break;
3616                 }
3617                 if(bookHit) { // [HGM] book: simulate book reply
3618                     static char bookMove[MSG_SIZ]; // a bit generous?
3619
3620                     programStats.nodes = programStats.depth = programStats.time =
3621                     programStats.score = programStats.got_only_move = 0;
3622                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3623
3624                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3625                     strcat(bookMove, bookHit);
3626                     HandleMachineMove(bookMove, &first);
3627                 }
3628                 continue;
3629             }
3630
3631             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3632                  started == STARTED_HOLDINGS ||
3633                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3634                 /* Accumulate characters in move list or board */
3635                 parse[parse_pos++] = buf[i];
3636             }
3637
3638             /* Start of game messages.  Mostly we detect start of game
3639                when the first board image arrives.  On some versions
3640                of the ICS, though, we need to do a "refresh" after starting
3641                to observe in order to get the current board right away. */
3642             if (looking_at(buf, &i, "Adding game * to observation list")) {
3643                 started = STARTED_OBSERVE;
3644                 continue;
3645             }
3646
3647             /* Handle auto-observe */
3648             if (appData.autoObserve &&
3649                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3650                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3651                 char *player;
3652                 /* Choose the player that was highlighted, if any. */
3653                 if (star_match[0][0] == '\033' ||
3654                     star_match[1][0] != '\033') {
3655                     player = star_match[0];
3656                 } else {
3657                     player = star_match[2];
3658                 }
3659                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3660                         ics_prefix, StripHighlightAndTitle(player));
3661                 SendToICS(str);
3662
3663                 /* Save ratings from notify string */
3664                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3665                 player1Rating = string_to_rating(star_match[1]);
3666                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3667                 player2Rating = string_to_rating(star_match[3]);
3668
3669                 if (appData.debugMode)
3670                   fprintf(debugFP,
3671                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3672                           player1Name, player1Rating,
3673                           player2Name, player2Rating);
3674
3675                 continue;
3676             }
3677
3678             /* Deal with automatic examine mode after a game,
3679                and with IcsObserving -> IcsExamining transition */
3680             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3681                 looking_at(buf, &i, "has made you an examiner of game *")) {
3682
3683                 int gamenum = atoi(star_match[0]);
3684                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3685                     gamenum == ics_gamenum) {
3686                     /* We were already playing or observing this game;
3687                        no need to refetch history */
3688                     gameMode = IcsExamining;
3689                     if (pausing) {
3690                         pauseExamForwardMostMove = forwardMostMove;
3691                     } else if (currentMove < forwardMostMove) {
3692                         ForwardInner(forwardMostMove);
3693                     }
3694                 } else {
3695                     /* I don't think this case really can happen */
3696                     SendToICS(ics_prefix);
3697                     SendToICS("refresh\n");
3698                 }
3699                 continue;
3700             }
3701
3702             /* Error messages */
3703 //          if (ics_user_moved) {
3704             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3705                 if (looking_at(buf, &i, "Illegal move") ||
3706                     looking_at(buf, &i, "Not a legal move") ||
3707                     looking_at(buf, &i, "Your king is in check") ||
3708                     looking_at(buf, &i, "It isn't your turn") ||
3709                     looking_at(buf, &i, "It is not your move")) {
3710                     /* Illegal move */
3711                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3712                         currentMove = forwardMostMove-1;
3713                         DisplayMove(currentMove - 1); /* before DMError */
3714                         DrawPosition(FALSE, boards[currentMove]);
3715                         SwitchClocks(forwardMostMove-1); // [HGM] race
3716                         DisplayBothClocks();
3717                     }
3718                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3719                     ics_user_moved = 0;
3720                     continue;
3721                 }
3722             }
3723
3724             if (looking_at(buf, &i, "still have time") ||
3725                 looking_at(buf, &i, "not out of time") ||
3726                 looking_at(buf, &i, "either player is out of time") ||
3727                 looking_at(buf, &i, "has timeseal; checking")) {
3728                 /* We must have called his flag a little too soon */
3729                 whiteFlag = blackFlag = FALSE;
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "added * seconds to") ||
3734                 looking_at(buf, &i, "seconds were added to")) {
3735                 /* Update the clocks */
3736                 SendToICS(ics_prefix);
3737                 SendToICS("refresh\n");
3738                 continue;
3739             }
3740
3741             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3742                 ics_clock_paused = TRUE;
3743                 StopClocks();
3744                 continue;
3745             }
3746
3747             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3748                 ics_clock_paused = FALSE;
3749                 StartClocks();
3750                 continue;
3751             }
3752
3753             /* Grab player ratings from the Creating: message.
3754                Note we have to check for the special case when
3755                the ICS inserts things like [white] or [black]. */
3756             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3757                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3758                 /* star_matches:
3759                    0    player 1 name (not necessarily white)
3760                    1    player 1 rating
3761                    2    empty, white, or black (IGNORED)
3762                    3    player 2 name (not necessarily black)
3763                    4    player 2 rating
3764
3765                    The names/ratings are sorted out when the game
3766                    actually starts (below).
3767                 */
3768                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3769                 player1Rating = string_to_rating(star_match[1]);
3770                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3771                 player2Rating = string_to_rating(star_match[4]);
3772
3773                 if (appData.debugMode)
3774                   fprintf(debugFP,
3775                           "Ratings from 'Creating:' %s %d, %s %d\n",
3776                           player1Name, player1Rating,
3777                           player2Name, player2Rating);
3778
3779                 continue;
3780             }
3781
3782             /* Improved generic start/end-of-game messages */
3783             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3784                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3785                 /* If tkind == 0: */
3786                 /* star_match[0] is the game number */
3787                 /*           [1] is the white player's name */
3788                 /*           [2] is the black player's name */
3789                 /* For end-of-game: */
3790                 /*           [3] is the reason for the game end */
3791                 /*           [4] is a PGN end game-token, preceded by " " */
3792                 /* For start-of-game: */
3793                 /*           [3] begins with "Creating" or "Continuing" */
3794                 /*           [4] is " *" or empty (don't care). */
3795                 int gamenum = atoi(star_match[0]);
3796                 char *whitename, *blackname, *why, *endtoken;
3797                 ChessMove endtype = EndOfFile;
3798
3799                 if (tkind == 0) {
3800                   whitename = star_match[1];
3801                   blackname = star_match[2];
3802                   why = star_match[3];
3803                   endtoken = star_match[4];
3804                 } else {
3805                   whitename = star_match[1];
3806                   blackname = star_match[3];
3807                   why = star_match[5];
3808                   endtoken = star_match[6];
3809                 }
3810
3811                 /* Game start messages */
3812                 if (strncmp(why, "Creating ", 9) == 0 ||
3813                     strncmp(why, "Continuing ", 11) == 0) {
3814                     gs_gamenum = gamenum;
3815                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3816                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3817 #if ZIPPY
3818                     if (appData.zippyPlay) {
3819                         ZippyGameStart(whitename, blackname);
3820                     }
3821 #endif /*ZIPPY*/
3822                     partnerBoardValid = FALSE; // [HGM] bughouse
3823                     continue;
3824                 }
3825
3826                 /* Game end messages */
3827                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3828                     ics_gamenum != gamenum) {
3829                     continue;
3830                 }
3831                 while (endtoken[0] == ' ') endtoken++;
3832                 switch (endtoken[0]) {
3833                   case '*':
3834                   default:
3835                     endtype = GameUnfinished;
3836                     break;
3837                   case '0':
3838                     endtype = BlackWins;
3839                     break;
3840                   case '1':
3841                     if (endtoken[1] == '/')
3842                       endtype = GameIsDrawn;
3843                     else
3844                       endtype = WhiteWins;
3845                     break;
3846                 }
3847                 GameEnds(endtype, why, GE_ICS);
3848 #if ZIPPY
3849                 if (appData.zippyPlay && first.initDone) {
3850                     ZippyGameEnd(endtype, why);
3851                     if (first.pr == NULL) {
3852                       /* Start the next process early so that we'll
3853                          be ready for the next challenge */
3854                       StartChessProgram(&first);
3855                     }
3856                     /* Send "new" early, in case this command takes
3857                        a long time to finish, so that we'll be ready
3858                        for the next challenge. */
3859                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3860                     Reset(TRUE, TRUE);
3861                 }
3862 #endif /*ZIPPY*/
3863                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3864                 continue;
3865             }
3866
3867             if (looking_at(buf, &i, "Removing game * from observation") ||
3868                 looking_at(buf, &i, "no longer observing game *") ||
3869                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3870                 if (gameMode == IcsObserving &&
3871                     atoi(star_match[0]) == ics_gamenum)
3872                   {
3873                       /* icsEngineAnalyze */
3874                       if (appData.icsEngineAnalyze) {
3875                             ExitAnalyzeMode();
3876                             ModeHighlight();
3877                       }
3878                       StopClocks();
3879                       gameMode = IcsIdle;
3880                       ics_gamenum = -1;
3881                       ics_user_moved = FALSE;
3882                   }
3883                 continue;
3884             }
3885
3886             if (looking_at(buf, &i, "no longer examining game *")) {
3887                 if (gameMode == IcsExamining &&
3888                     atoi(star_match[0]) == ics_gamenum)
3889                   {
3890                       gameMode = IcsIdle;
3891                       ics_gamenum = -1;
3892                       ics_user_moved = FALSE;
3893                   }
3894                 continue;
3895             }
3896
3897             /* Advance leftover_start past any newlines we find,
3898                so only partial lines can get reparsed */
3899             if (looking_at(buf, &i, "\n")) {
3900                 prevColor = curColor;
3901                 if (curColor != ColorNormal) {
3902                     if (oldi > next_out) {
3903                         SendToPlayer(&buf[next_out], oldi - next_out);
3904                         next_out = oldi;
3905                     }
3906                     Colorize(ColorNormal, FALSE);
3907                     curColor = ColorNormal;
3908                 }
3909                 if (started == STARTED_BOARD) {
3910                     started = STARTED_NONE;
3911                     parse[parse_pos] = NULLCHAR;
3912                     ParseBoard12(parse);
3913                     ics_user_moved = 0;
3914
3915                     /* Send premove here */
3916                     if (appData.premove) {
3917                       char str[MSG_SIZ];
3918                       if (currentMove == 0 &&
3919                           gameMode == IcsPlayingWhite &&
3920                           appData.premoveWhite) {
3921                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3922                         if (appData.debugMode)
3923                           fprintf(debugFP, "Sending premove:\n");
3924                         SendToICS(str);
3925                       } else if (currentMove == 1 &&
3926                                  gameMode == IcsPlayingBlack &&
3927                                  appData.premoveBlack) {
3928                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3929                         if (appData.debugMode)
3930                           fprintf(debugFP, "Sending premove:\n");
3931                         SendToICS(str);
3932                       } else if (gotPremove) {
3933                         gotPremove = 0;
3934                         ClearPremoveHighlights();
3935                         if (appData.debugMode)
3936                           fprintf(debugFP, "Sending premove:\n");
3937                           UserMoveEvent(premoveFromX, premoveFromY,
3938                                         premoveToX, premoveToY,
3939                                         premovePromoChar);
3940                       }
3941                     }
3942
3943                     /* Usually suppress following prompt */
3944                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3945                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3946                         if (looking_at(buf, &i, "*% ")) {
3947                             savingComment = FALSE;
3948                             suppressKibitz = 0;
3949                         }
3950                     }
3951                     next_out = i;
3952                 } else if (started == STARTED_HOLDINGS) {
3953                     int gamenum;
3954                     char new_piece[MSG_SIZ];
3955                     started = STARTED_NONE;
3956                     parse[parse_pos] = NULLCHAR;
3957                     if (appData.debugMode)
3958                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3959                                                         parse, currentMove);
3960                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3961                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3962                         if (gameInfo.variant == VariantNormal) {
3963                           /* [HGM] We seem to switch variant during a game!
3964                            * Presumably no holdings were displayed, so we have
3965                            * to move the position two files to the right to
3966                            * create room for them!
3967                            */
3968                           VariantClass newVariant;
3969                           switch(gameInfo.boardWidth) { // base guess on board width
3970                                 case 9:  newVariant = VariantShogi; break;
3971                                 case 10: newVariant = VariantGreat; break;
3972                                 default: newVariant = VariantCrazyhouse; break;
3973                           }
3974                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3975                           /* Get a move list just to see the header, which
3976                              will tell us whether this is really bug or zh */
3977                           if (ics_getting_history == H_FALSE) {
3978                             ics_getting_history = H_REQUESTED;
3979                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3980                             SendToICS(str);
3981                           }
3982                         }
3983                         new_piece[0] = NULLCHAR;
3984                         sscanf(parse, "game %d white [%s black [%s <- %s",
3985                                &gamenum, white_holding, black_holding,
3986                                new_piece);
3987                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3988                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3989                         /* [HGM] copy holdings to board holdings area */
3990                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3991                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3992                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3993 #if ZIPPY
3994                         if (appData.zippyPlay && first.initDone) {
3995                             ZippyHoldings(white_holding, black_holding,
3996                                           new_piece);
3997                         }
3998 #endif /*ZIPPY*/
3999                         if (tinyLayout || smallLayout) {
4000                             char wh[16], bh[16];
4001                             PackHolding(wh, white_holding);
4002                             PackHolding(bh, black_holding);
4003                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4004                                     gameInfo.white, gameInfo.black);
4005                         } else {
4006                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4007                                     gameInfo.white, white_holding,
4008                                     gameInfo.black, black_holding);
4009                         }
4010                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4011                         DrawPosition(FALSE, boards[currentMove]);
4012                         DisplayTitle(str);
4013                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4014                         sscanf(parse, "game %d white [%s black [%s <- %s",
4015                                &gamenum, white_holding, black_holding,
4016                                new_piece);
4017                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4018                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4019                         /* [HGM] copy holdings to partner-board holdings area */
4020                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4021                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4022                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4023                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4024                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4025                       }
4026                     }
4027                     /* Suppress following prompt */
4028                     if (looking_at(buf, &i, "*% ")) {
4029                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4030                         savingComment = FALSE;
4031                         suppressKibitz = 0;
4032                     }
4033                     next_out = i;
4034                 }
4035                 continue;
4036             }
4037
4038             i++;                /* skip unparsed character and loop back */
4039         }
4040
4041         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4042 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4043 //          SendToPlayer(&buf[next_out], i - next_out);
4044             started != STARTED_HOLDINGS && leftover_start > next_out) {
4045             SendToPlayer(&buf[next_out], leftover_start - next_out);
4046             next_out = i;
4047         }
4048
4049         leftover_len = buf_len - leftover_start;
4050         /* if buffer ends with something we couldn't parse,
4051            reparse it after appending the next read */
4052
4053     } else if (count == 0) {
4054         RemoveInputSource(isr);
4055         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4056     } else {
4057         DisplayFatalError(_("Error reading from ICS"), error, 1);
4058     }
4059 }
4060
4061
4062 /* Board style 12 looks like this:
4063
4064    <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
4065
4066  * The "<12> " is stripped before it gets to this routine.  The two
4067  * trailing 0's (flip state and clock ticking) are later addition, and
4068  * some chess servers may not have them, or may have only the first.
4069  * Additional trailing fields may be added in the future.
4070  */
4071
4072 #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"
4073
4074 #define RELATION_OBSERVING_PLAYED    0
4075 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4076 #define RELATION_PLAYING_MYMOVE      1
4077 #define RELATION_PLAYING_NOTMYMOVE  -1
4078 #define RELATION_EXAMINING           2
4079 #define RELATION_ISOLATED_BOARD     -3
4080 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4081
4082 void
4083 ParseBoard12(string)
4084      char *string;
4085 {
4086     GameMode newGameMode;
4087     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4088     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4089     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4090     char to_play, board_chars[200];
4091     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4092     char black[32], white[32];
4093     Board board;
4094     int prevMove = currentMove;
4095     int ticking = 2;
4096     ChessMove moveType;
4097     int fromX, fromY, toX, toY;
4098     char promoChar;
4099     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4100     char *bookHit = NULL; // [HGM] book
4101     Boolean weird = FALSE, reqFlag = FALSE;
4102
4103     fromX = fromY = toX = toY = -1;
4104
4105     newGame = FALSE;
4106
4107     if (appData.debugMode)
4108       fprintf(debugFP, _("Parsing board: %s\n"), string);
4109
4110     move_str[0] = NULLCHAR;
4111     elapsed_time[0] = NULLCHAR;
4112     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4113         int  i = 0, j;
4114         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4115             if(string[i] == ' ') { ranks++; files = 0; }
4116             else files++;
4117             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4118             i++;
4119         }
4120         for(j = 0; j <i; j++) board_chars[j] = string[j];
4121         board_chars[i] = '\0';
4122         string += i + 1;
4123     }
4124     n = sscanf(string, PATTERN, &to_play, &double_push,
4125                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4126                &gamenum, white, black, &relation, &basetime, &increment,
4127                &white_stren, &black_stren, &white_time, &black_time,
4128                &moveNum, str, elapsed_time, move_str, &ics_flip,
4129                &ticking);
4130
4131     if (n < 21) {
4132         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4133         DisplayError(str, 0);
4134         return;
4135     }
4136
4137     /* Convert the move number to internal form */
4138     moveNum = (moveNum - 1) * 2;
4139     if (to_play == 'B') moveNum++;
4140     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4141       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4142                         0, 1);
4143       return;
4144     }
4145
4146     switch (relation) {
4147       case RELATION_OBSERVING_PLAYED:
4148       case RELATION_OBSERVING_STATIC:
4149         if (gamenum == -1) {
4150             /* Old ICC buglet */
4151             relation = RELATION_OBSERVING_STATIC;
4152         }
4153         newGameMode = IcsObserving;
4154         break;
4155       case RELATION_PLAYING_MYMOVE:
4156       case RELATION_PLAYING_NOTMYMOVE:
4157         newGameMode =
4158           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4159             IcsPlayingWhite : IcsPlayingBlack;
4160         break;
4161       case RELATION_EXAMINING:
4162         newGameMode = IcsExamining;
4163         break;
4164       case RELATION_ISOLATED_BOARD:
4165       default:
4166         /* Just display this board.  If user was doing something else,
4167            we will forget about it until the next board comes. */
4168         newGameMode = IcsIdle;
4169         break;
4170       case RELATION_STARTING_POSITION:
4171         newGameMode = gameMode;
4172         break;
4173     }
4174
4175     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4176          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4177       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4178       char *toSqr;
4179       for (k = 0; k < ranks; k++) {
4180         for (j = 0; j < files; j++)
4181           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4182         if(gameInfo.holdingsWidth > 1) {
4183              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4184              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4185         }
4186       }
4187       CopyBoard(partnerBoard, board);
4188       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4189         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4190         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4192       if(toSqr = strchr(str, '-')) {
4193         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4194         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4195       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4196       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4197       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4198       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4199       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4200       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4201                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4202       DisplayMessage(partnerStatus, "");
4203         partnerBoardValid = TRUE;
4204       return;
4205     }
4206
4207     /* Modify behavior for initial board display on move listing
4208        of wild games.
4209        */
4210     switch (ics_getting_history) {
4211       case H_FALSE:
4212       case H_REQUESTED:
4213         break;
4214       case H_GOT_REQ_HEADER:
4215       case H_GOT_UNREQ_HEADER:
4216         /* This is the initial position of the current game */
4217         gamenum = ics_gamenum;
4218         moveNum = 0;            /* old ICS bug workaround */
4219         if (to_play == 'B') {
4220           startedFromSetupPosition = TRUE;
4221           blackPlaysFirst = TRUE;
4222           moveNum = 1;
4223           if (forwardMostMove == 0) forwardMostMove = 1;
4224           if (backwardMostMove == 0) backwardMostMove = 1;
4225           if (currentMove == 0) currentMove = 1;
4226         }
4227         newGameMode = gameMode;
4228         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4229         break;
4230       case H_GOT_UNWANTED_HEADER:
4231         /* This is an initial board that we don't want */
4232         return;
4233       case H_GETTING_MOVES:
4234         /* Should not happen */
4235         DisplayError(_("Error gathering move list: extra board"), 0);
4236         ics_getting_history = H_FALSE;
4237         return;
4238     }
4239
4240    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4241                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4242      /* [HGM] We seem to have switched variant unexpectedly
4243       * Try to guess new variant from board size
4244       */
4245           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4246           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4247           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4248           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4249           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4250           if(!weird) newVariant = VariantNormal;
4251           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4252           /* Get a move list just to see the header, which
4253              will tell us whether this is really bug or zh */
4254           if (ics_getting_history == H_FALSE) {
4255             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4256             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4257             SendToICS(str);
4258           }
4259     }
4260
4261     /* Take action if this is the first board of a new game, or of a
4262        different game than is currently being displayed.  */
4263     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4264         relation == RELATION_ISOLATED_BOARD) {
4265
4266         /* Forget the old game and get the history (if any) of the new one */
4267         if (gameMode != BeginningOfGame) {
4268           Reset(TRUE, TRUE);
4269         }
4270         newGame = TRUE;
4271         if (appData.autoRaiseBoard) BoardToTop();
4272         prevMove = -3;
4273         if (gamenum == -1) {
4274             newGameMode = IcsIdle;
4275         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4276                    appData.getMoveList && !reqFlag) {
4277             /* Need to get game history */
4278             ics_getting_history = H_REQUESTED;
4279             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280             SendToICS(str);
4281         }
4282
4283         /* Initially flip the board to have black on the bottom if playing
4284            black or if the ICS flip flag is set, but let the user change
4285            it with the Flip View button. */
4286         flipView = appData.autoFlipView ?
4287           (newGameMode == IcsPlayingBlack) || ics_flip :
4288           appData.flipView;
4289
4290         /* Done with values from previous mode; copy in new ones */
4291         gameMode = newGameMode;
4292         ModeHighlight();
4293         ics_gamenum = gamenum;
4294         if (gamenum == gs_gamenum) {
4295             int klen = strlen(gs_kind);
4296             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4297             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4298             gameInfo.event = StrSave(str);
4299         } else {
4300             gameInfo.event = StrSave("ICS game");
4301         }
4302         gameInfo.site = StrSave(appData.icsHost);
4303         gameInfo.date = PGNDate();
4304         gameInfo.round = StrSave("-");
4305         gameInfo.white = StrSave(white);
4306         gameInfo.black = StrSave(black);
4307         timeControl = basetime * 60 * 1000;
4308         timeControl_2 = 0;
4309         timeIncrement = increment * 1000;
4310         movesPerSession = 0;
4311         gameInfo.timeControl = TimeControlTagValue();
4312         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4313   if (appData.debugMode) {
4314     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4315     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4316     setbuf(debugFP, NULL);
4317   }
4318
4319         gameInfo.outOfBook = NULL;
4320
4321         /* Do we have the ratings? */
4322         if (strcmp(player1Name, white) == 0 &&
4323             strcmp(player2Name, black) == 0) {
4324             if (appData.debugMode)
4325               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4326                       player1Rating, player2Rating);
4327             gameInfo.whiteRating = player1Rating;
4328             gameInfo.blackRating = player2Rating;
4329         } else if (strcmp(player2Name, white) == 0 &&
4330                    strcmp(player1Name, black) == 0) {
4331             if (appData.debugMode)
4332               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4333                       player2Rating, player1Rating);
4334             gameInfo.whiteRating = player2Rating;
4335             gameInfo.blackRating = player1Rating;
4336         }
4337         player1Name[0] = player2Name[0] = NULLCHAR;
4338
4339         /* Silence shouts if requested */
4340         if (appData.quietPlay &&
4341             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4342             SendToICS(ics_prefix);
4343             SendToICS("set shout 0\n");
4344         }
4345     }
4346
4347     /* Deal with midgame name changes */
4348     if (!newGame) {
4349         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4350             if (gameInfo.white) free(gameInfo.white);
4351             gameInfo.white = StrSave(white);
4352         }
4353         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4354             if (gameInfo.black) free(gameInfo.black);
4355             gameInfo.black = StrSave(black);
4356         }
4357     }
4358
4359     /* Throw away game result if anything actually changes in examine mode */
4360     if (gameMode == IcsExamining && !newGame) {
4361         gameInfo.result = GameUnfinished;
4362         if (gameInfo.resultDetails != NULL) {
4363             free(gameInfo.resultDetails);
4364             gameInfo.resultDetails = NULL;
4365         }
4366     }
4367
4368     /* In pausing && IcsExamining mode, we ignore boards coming
4369        in if they are in a different variation than we are. */
4370     if (pauseExamInvalid) return;
4371     if (pausing && gameMode == IcsExamining) {
4372         if (moveNum <= pauseExamForwardMostMove) {
4373             pauseExamInvalid = TRUE;
4374             forwardMostMove = pauseExamForwardMostMove;
4375             return;
4376         }
4377     }
4378
4379   if (appData.debugMode) {
4380     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4381   }
4382     /* Parse the board */
4383     for (k = 0; k < ranks; k++) {
4384       for (j = 0; j < files; j++)
4385         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4386       if(gameInfo.holdingsWidth > 1) {
4387            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4388            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4389       }
4390     }
4391     CopyBoard(boards[moveNum], board);
4392     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4393     if (moveNum == 0) {
4394         startedFromSetupPosition =
4395           !CompareBoards(board, initialPosition);
4396         if(startedFromSetupPosition)
4397             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4398     }
4399
4400     /* [HGM] Set castling rights. Take the outermost Rooks,
4401        to make it also work for FRC opening positions. Note that board12
4402        is really defective for later FRC positions, as it has no way to
4403        indicate which Rook can castle if they are on the same side of King.
4404        For the initial position we grant rights to the outermost Rooks,
4405        and remember thos rights, and we then copy them on positions
4406        later in an FRC game. This means WB might not recognize castlings with
4407        Rooks that have moved back to their original position as illegal,
4408        but in ICS mode that is not its job anyway.
4409     */
4410     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4411     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4412
4413         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4414             if(board[0][i] == WhiteRook) j = i;
4415         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4416         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4417             if(board[0][i] == WhiteRook) j = i;
4418         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4419         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4420             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4421         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4422         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4423             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4424         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4425
4426         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4427         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4428             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4429         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4430             if(board[BOARD_HEIGHT-1][k] == bKing)
4431                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4432         if(gameInfo.variant == VariantTwoKings) {
4433             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4434             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4435             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4436         }
4437     } else { int r;
4438         r = boards[moveNum][CASTLING][0] = initialRights[0];
4439         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4440         r = boards[moveNum][CASTLING][1] = initialRights[1];
4441         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4442         r = boards[moveNum][CASTLING][3] = initialRights[3];
4443         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4444         r = boards[moveNum][CASTLING][4] = initialRights[4];
4445         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4446         /* wildcastle kludge: always assume King has rights */
4447         r = boards[moveNum][CASTLING][2] = initialRights[2];
4448         r = boards[moveNum][CASTLING][5] = initialRights[5];
4449     }
4450     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4451     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4452
4453
4454     if (ics_getting_history == H_GOT_REQ_HEADER ||
4455         ics_getting_history == H_GOT_UNREQ_HEADER) {
4456         /* This was an initial position from a move list, not
4457            the current position */
4458         return;
4459     }
4460
4461     /* Update currentMove and known move number limits */
4462     newMove = newGame || moveNum > forwardMostMove;
4463
4464     if (newGame) {
4465         forwardMostMove = backwardMostMove = currentMove = moveNum;
4466         if (gameMode == IcsExamining && moveNum == 0) {
4467           /* Workaround for ICS limitation: we are not told the wild
4468              type when starting to examine a game.  But if we ask for
4469              the move list, the move list header will tell us */
4470             ics_getting_history = H_REQUESTED;
4471             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4472             SendToICS(str);
4473         }
4474     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4475                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4476 #if ZIPPY
4477         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4478         /* [HGM] applied this also to an engine that is silently watching        */
4479         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4480             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4481             gameInfo.variant == currentlyInitializedVariant) {
4482           takeback = forwardMostMove - moveNum;
4483           for (i = 0; i < takeback; i++) {
4484             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4485             SendToProgram("undo\n", &first);
4486           }
4487         }
4488 #endif
4489
4490         forwardMostMove = moveNum;
4491         if (!pausing || currentMove > forwardMostMove)
4492           currentMove = forwardMostMove;
4493     } else {
4494         /* New part of history that is not contiguous with old part */
4495         if (pausing && gameMode == IcsExamining) {
4496             pauseExamInvalid = TRUE;
4497             forwardMostMove = pauseExamForwardMostMove;
4498             return;
4499         }
4500         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4501 #if ZIPPY
4502             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4503                 // [HGM] when we will receive the move list we now request, it will be
4504                 // fed to the engine from the first move on. So if the engine is not
4505                 // in the initial position now, bring it there.
4506                 InitChessProgram(&first, 0);
4507             }
4508 #endif
4509             ics_getting_history = H_REQUESTED;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512         }
4513         forwardMostMove = backwardMostMove = currentMove = moveNum;
4514     }
4515
4516     /* Update the clocks */
4517     if (strchr(elapsed_time, '.')) {
4518       /* Time is in ms */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4521     } else {
4522       /* Time is in seconds */
4523       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4524       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4525     }
4526
4527
4528 #if ZIPPY
4529     if (appData.zippyPlay && newGame &&
4530         gameMode != IcsObserving && gameMode != IcsIdle &&
4531         gameMode != IcsExamining)
4532       ZippyFirstBoard(moveNum, basetime, increment);
4533 #endif
4534
4535     /* Put the move on the move list, first converting
4536        to canonical algebraic form. */
4537     if (moveNum > 0) {
4538   if (appData.debugMode) {
4539     if (appData.debugMode) { int f = forwardMostMove;
4540         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4541                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4542                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4543     }
4544     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4545     fprintf(debugFP, "moveNum = %d\n", moveNum);
4546     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4547     setbuf(debugFP, NULL);
4548   }
4549         if (moveNum <= backwardMostMove) {
4550             /* We don't know what the board looked like before
4551                this move.  Punt. */
4552           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4553             strcat(parseList[moveNum - 1], " ");
4554             strcat(parseList[moveNum - 1], elapsed_time);
4555             moveList[moveNum - 1][0] = NULLCHAR;
4556         } else if (strcmp(move_str, "none") == 0) {
4557             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4558             /* Again, we don't know what the board looked like;
4559                this is really the start of the game. */
4560             parseList[moveNum - 1][0] = NULLCHAR;
4561             moveList[moveNum - 1][0] = NULLCHAR;
4562             backwardMostMove = moveNum;
4563             startedFromSetupPosition = TRUE;
4564             fromX = fromY = toX = toY = -1;
4565         } else {
4566           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4567           //                 So we parse the long-algebraic move string in stead of the SAN move
4568           int valid; char buf[MSG_SIZ], *prom;
4569
4570           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4571                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4572           // str looks something like "Q/a1-a2"; kill the slash
4573           if(str[1] == '/')
4574             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4575           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4576           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4577                 strcat(buf, prom); // long move lacks promo specification!
4578           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4579                 if(appData.debugMode)
4580                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4581                 safeStrCpy(move_str, buf, MSG_SIZ);
4582           }
4583           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4584                                 &fromX, &fromY, &toX, &toY, &promoChar)
4585                || ParseOneMove(buf, moveNum - 1, &moveType,
4586                                 &fromX, &fromY, &toX, &toY, &promoChar);
4587           // end of long SAN patch
4588           if (valid) {
4589             (void) CoordsToAlgebraic(boards[moveNum - 1],
4590                                      PosFlags(moveNum - 1),
4591                                      fromY, fromX, toY, toX, promoChar,
4592                                      parseList[moveNum-1]);
4593             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4594               case MT_NONE:
4595               case MT_STALEMATE:
4596               default:
4597                 break;
4598               case MT_CHECK:
4599                 if(gameInfo.variant != VariantShogi)
4600                     strcat(parseList[moveNum - 1], "+");
4601                 break;
4602               case MT_CHECKMATE:
4603               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4604                 strcat(parseList[moveNum - 1], "#");
4605                 break;
4606             }
4607             strcat(parseList[moveNum - 1], " ");
4608             strcat(parseList[moveNum - 1], elapsed_time);
4609             /* currentMoveString is set as a side-effect of ParseOneMove */
4610             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4611             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4612             strcat(moveList[moveNum - 1], "\n");
4613
4614             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4615                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4616               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4617                 ChessSquare old, new = boards[moveNum][k][j];
4618                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4619                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4620                   if(old == new) continue;
4621                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4622                   else if(new == WhiteWazir || new == BlackWazir) {
4623                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4624                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4625                       else boards[moveNum][k][j] = old; // preserve type of Gold
4626                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4627                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4628               }
4629           } else {
4630             /* Move from ICS was illegal!?  Punt. */
4631             if (appData.debugMode) {
4632               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4633               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4634             }
4635             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             moveList[moveNum - 1][0] = NULLCHAR;
4639             fromX = fromY = toX = toY = -1;
4640           }
4641         }
4642   if (appData.debugMode) {
4643     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4644     setbuf(debugFP, NULL);
4645   }
4646
4647 #if ZIPPY
4648         /* Send move to chess program (BEFORE animating it). */
4649         if (appData.zippyPlay && !newGame && newMove &&
4650            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4651
4652             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4653                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4654                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4655                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4656                             move_str);
4657                     DisplayError(str, 0);
4658                 } else {
4659                     if (first.sendTime) {
4660                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4661                     }
4662                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4663                     if (firstMove && !bookHit) {
4664                         firstMove = FALSE;
4665                         if (first.useColors) {
4666                           SendToProgram(gameMode == IcsPlayingWhite ?
4667                                         "white\ngo\n" :
4668                                         "black\ngo\n", &first);
4669                         } else {
4670                           SendToProgram("go\n", &first);
4671                         }
4672                         first.maybeThinking = TRUE;
4673                     }
4674                 }
4675             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4676               if (moveList[moveNum - 1][0] == NULLCHAR) {
4677                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4678                 DisplayError(str, 0);
4679               } else {
4680                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4681                 SendMoveToProgram(moveNum - 1, &first);
4682               }
4683             }
4684         }
4685 #endif
4686     }
4687
4688     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4689         /* If move comes from a remote source, animate it.  If it
4690            isn't remote, it will have already been animated. */
4691         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4692             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4693         }
4694         if (!pausing && appData.highlightLastMove) {
4695             SetHighlights(fromX, fromY, toX, toY);
4696         }
4697     }
4698
4699     /* Start the clocks */
4700     whiteFlag = blackFlag = FALSE;
4701     appData.clockMode = !(basetime == 0 && increment == 0);
4702     if (ticking == 0) {
4703       ics_clock_paused = TRUE;
4704       StopClocks();
4705     } else if (ticking == 1) {
4706       ics_clock_paused = FALSE;
4707     }
4708     if (gameMode == IcsIdle ||
4709         relation == RELATION_OBSERVING_STATIC ||
4710         relation == RELATION_EXAMINING ||
4711         ics_clock_paused)
4712       DisplayBothClocks();
4713     else
4714       StartClocks();
4715
4716     /* Display opponents and material strengths */
4717     if (gameInfo.variant != VariantBughouse &&
4718         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4719         if (tinyLayout || smallLayout) {
4720             if(gameInfo.variant == VariantNormal)
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment);
4724             else
4725               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4726                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4727                     basetime, increment, (int) gameInfo.variant);
4728         } else {
4729             if(gameInfo.variant == VariantNormal)
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment);
4733             else
4734               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4735                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4736                     basetime, increment, VariantName(gameInfo.variant));
4737         }
4738         DisplayTitle(str);
4739   if (appData.debugMode) {
4740     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4741   }
4742     }
4743
4744
4745     /* Display the board */
4746     if (!pausing && !appData.noGUI) {
4747
4748       if (appData.premove)
4749           if (!gotPremove ||
4750              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4751              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4752               ClearPremoveHighlights();
4753
4754       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4755         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4756       DrawPosition(j, boards[currentMove]);
4757
4758       DisplayMove(moveNum - 1);
4759       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4760             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4761               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4762         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4763       }
4764     }
4765
4766     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4767 #if ZIPPY
4768     if(bookHit) { // [HGM] book: simulate book reply
4769         static char bookMove[MSG_SIZ]; // a bit generous?
4770
4771         programStats.nodes = programStats.depth = programStats.time =
4772         programStats.score = programStats.got_only_move = 0;
4773         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4774
4775         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4776         strcat(bookMove, bookHit);
4777         HandleMachineMove(bookMove, &first);
4778     }
4779 #endif
4780 }
4781
4782 void
4783 GetMoveListEvent()
4784 {
4785     char buf[MSG_SIZ];
4786     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4787         ics_getting_history = H_REQUESTED;
4788         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4789         SendToICS(buf);
4790     }
4791 }
4792
4793 void
4794 AnalysisPeriodicEvent(force)
4795      int force;
4796 {
4797     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4798          && !force) || !appData.periodicUpdates)
4799       return;
4800
4801     /* Send . command to Crafty to collect stats */
4802     SendToProgram(".\n", &first);
4803
4804     /* Don't send another until we get a response (this makes
4805        us stop sending to old Crafty's which don't understand
4806        the "." command (sending illegal cmds resets node count & time,
4807        which looks bad)) */
4808     programStats.ok_to_send = 0;
4809 }
4810
4811 void ics_update_width(new_width)
4812         int new_width;
4813 {
4814         ics_printf("set width %d\n", new_width);
4815 }
4816
4817 void
4818 SendMoveToProgram(moveNum, cps)
4819      int moveNum;
4820      ChessProgramState *cps;
4821 {
4822     char buf[MSG_SIZ];
4823
4824     if (cps->useUsermove) {
4825       SendToProgram("usermove ", cps);
4826     }
4827     if (cps->useSAN) {
4828       char *space;
4829       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4830         int len = space - parseList[moveNum];
4831         memcpy(buf, parseList[moveNum], len);
4832         buf[len++] = '\n';
4833         buf[len] = NULLCHAR;
4834       } else {
4835         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4836       }
4837       SendToProgram(buf, cps);
4838     } else {
4839       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4840         AlphaRank(moveList[moveNum], 4);
4841         SendToProgram(moveList[moveNum], cps);
4842         AlphaRank(moveList[moveNum], 4); // and back
4843       } else
4844       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4845        * the engine. It would be nice to have a better way to identify castle
4846        * moves here. */
4847       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4848                                                                          && cps->useOOCastle) {
4849         int fromX = moveList[moveNum][0] - AAA;
4850         int fromY = moveList[moveNum][1] - ONE;
4851         int toX = moveList[moveNum][2] - AAA;
4852         int toY = moveList[moveNum][3] - ONE;
4853         if((boards[moveNum][fromY][fromX] == WhiteKing
4854             && boards[moveNum][toY][toX] == WhiteRook)
4855            || (boards[moveNum][fromY][fromX] == BlackKing
4856                && boards[moveNum][toY][toX] == BlackRook)) {
4857           if(toX > fromX) SendToProgram("O-O\n", cps);
4858           else SendToProgram("O-O-O\n", cps);
4859         }
4860         else SendToProgram(moveList[moveNum], cps);
4861       }
4862       else SendToProgram(moveList[moveNum], cps);
4863       /* End of additions by Tord */
4864     }
4865
4866     /* [HGM] setting up the opening has brought engine in force mode! */
4867     /*       Send 'go' if we are in a mode where machine should play. */
4868     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4869         (gameMode == TwoMachinesPlay   ||
4870 #if ZIPPY
4871          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4872 #endif
4873          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4874         SendToProgram("go\n", cps);
4875   if (appData.debugMode) {
4876     fprintf(debugFP, "(extra)\n");
4877   }
4878     }
4879     setboardSpoiledMachineBlack = 0;
4880 }
4881
4882 void
4883 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4884      ChessMove moveType;
4885      int fromX, fromY, toX, toY;
4886      char promoChar;
4887 {
4888     char user_move[MSG_SIZ];
4889
4890     switch (moveType) {
4891       default:
4892         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4893                 (int)moveType, fromX, fromY, toX, toY);
4894         DisplayError(user_move + strlen("say "), 0);
4895         break;
4896       case WhiteKingSideCastle:
4897       case BlackKingSideCastle:
4898       case WhiteQueenSideCastleWild:
4899       case BlackQueenSideCastleWild:
4900       /* PUSH Fabien */
4901       case WhiteHSideCastleFR:
4902       case BlackHSideCastleFR:
4903       /* POP Fabien */
4904         snprintf(user_move, MSG_SIZ, "o-o\n");
4905         break;
4906       case WhiteQueenSideCastle:
4907       case BlackQueenSideCastle:
4908       case WhiteKingSideCastleWild:
4909       case BlackKingSideCastleWild:
4910       /* PUSH Fabien */
4911       case WhiteASideCastleFR:
4912       case BlackASideCastleFR:
4913       /* POP Fabien */
4914         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4915         break;
4916       case WhiteNonPromotion:
4917       case BlackNonPromotion:
4918         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4919         break;
4920       case WhitePromotion:
4921       case BlackPromotion:
4922         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4923           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteFerz));
4926         else if(gameInfo.variant == VariantGreat)
4927           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 PieceToChar(WhiteMan));
4930         else
4931           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4932                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4933                 promoChar);
4934         break;
4935       case WhiteDrop:
4936       case BlackDrop:
4937       drop:
4938         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4939                  ToUpper(PieceToChar((ChessSquare) fromX)),
4940                  AAA + toX, ONE + toY);
4941         break;
4942       case IllegalMove:  /* could be a variant we don't quite understand */
4943         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4944       case NormalMove:
4945       case WhiteCapturesEnPassant:
4946       case BlackCapturesEnPassant:
4947         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4948                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4949         break;
4950     }
4951     SendToICS(user_move);
4952     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4953         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4954 }
4955
4956 void
4957 UploadGameEvent()
4958 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4959     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4960     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4961     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4962         DisplayError("You cannot do this while you are playing or observing", 0);
4963         return;
4964     }
4965     if(gameMode != IcsExamining) { // is this ever not the case?
4966         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4967
4968         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4969           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4970         } else { // on FICS we must first go to general examine mode
4971           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4972         }
4973         if(gameInfo.variant != VariantNormal) {
4974             // try figure out wild number, as xboard names are not always valid on ICS
4975             for(i=1; i<=36; i++) {
4976               snprintf(buf, MSG_SIZ, "wild/%d", i);
4977                 if(StringToVariant(buf) == gameInfo.variant) break;
4978             }
4979             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4980             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4981             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4982         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4983         SendToICS(ics_prefix);
4984         SendToICS(buf);
4985         if(startedFromSetupPosition || backwardMostMove != 0) {
4986           fen = PositionToFEN(backwardMostMove, NULL);
4987           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4988             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4989             SendToICS(buf);
4990           } else { // FICS: everything has to set by separate bsetup commands
4991             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4992             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4993             SendToICS(buf);
4994             if(!WhiteOnMove(backwardMostMove)) {
4995                 SendToICS("bsetup tomove black\n");
4996             }
4997             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4998             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4999             SendToICS(buf);
5000             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5001             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5002             SendToICS(buf);
5003             i = boards[backwardMostMove][EP_STATUS];
5004             if(i >= 0) { // set e.p.
5005               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5006                 SendToICS(buf);
5007             }
5008             bsetup++;
5009           }
5010         }
5011       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5012             SendToICS("bsetup done\n"); // switch to normal examining.
5013     }
5014     for(i = backwardMostMove; i<last; i++) {
5015         char buf[20];
5016         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5017         SendToICS(buf);
5018     }
5019     SendToICS(ics_prefix);
5020     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5021 }
5022
5023 void
5024 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5025      int rf, ff, rt, ft;
5026      char promoChar;
5027      char move[7];
5028 {
5029     if (rf == DROP_RANK) {
5030       sprintf(move, "%c@%c%c\n",
5031                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5032     } else {
5033         if (promoChar == 'x' || promoChar == NULLCHAR) {
5034           sprintf(move, "%c%c%c%c\n",
5035                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5036         } else {
5037             sprintf(move, "%c%c%c%c%c\n",
5038                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5039         }
5040     }
5041 }
5042
5043 void
5044 ProcessICSInitScript(f)
5045      FILE *f;
5046 {
5047     char buf[MSG_SIZ];
5048
5049     while (fgets(buf, MSG_SIZ, f)) {
5050         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5051     }
5052
5053     fclose(f);
5054 }
5055
5056
5057 static int lastX, lastY, selectFlag, dragging;
5058
5059 void
5060 Sweep(int step)
5061 {
5062     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5063     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5064     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5065     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5066     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5067     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5068     do {
5069         promoSweep -= step;
5070         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5071         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5072         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5073         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5074         if(!step) step = 1;
5075     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5076             appData.testLegality && (promoSweep == king ||
5077             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5078     ChangeDragPiece(promoSweep);
5079 }
5080
5081 int PromoScroll(int x, int y)
5082 {
5083   int step = 0;
5084
5085   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5086   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5087   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5088   if(!step) return FALSE;
5089   lastX = x; lastY = y;
5090   if((promoSweep < BlackPawn) == flipView) step = -step;
5091   if(step > 0) selectFlag = 1;
5092   if(!selectFlag) Sweep(step);
5093   return FALSE;
5094 }
5095
5096 void
5097 NextPiece(int step)
5098 {
5099     ChessSquare piece = boards[currentMove][toY][toX];
5100     do {
5101         pieceSweep -= step;
5102         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5103         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5104         if(!step) step = -1;
5105     } while(PieceToChar(pieceSweep) == '.');
5106     boards[currentMove][toY][toX] = pieceSweep;
5107     DrawPosition(FALSE, boards[currentMove]);
5108     boards[currentMove][toY][toX] = piece;
5109 }
5110 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5111 void
5112 AlphaRank(char *move, int n)
5113 {
5114 //    char *p = move, c; int x, y;
5115
5116     if (appData.debugMode) {
5117         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5118     }
5119
5120     if(move[1]=='*' &&
5121        move[2]>='0' && move[2]<='9' &&
5122        move[3]>='a' && move[3]<='x'    ) {
5123         move[1] = '@';
5124         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5125         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5126     } else
5127     if(move[0]>='0' && move[0]<='9' &&
5128        move[1]>='a' && move[1]<='x' &&
5129        move[2]>='0' && move[2]<='9' &&
5130        move[3]>='a' && move[3]<='x'    ) {
5131         /* input move, Shogi -> normal */
5132         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5133         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5134         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5135         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5136     } else
5137     if(move[1]=='@' &&
5138        move[3]>='0' && move[3]<='9' &&
5139        move[2]>='a' && move[2]<='x'    ) {
5140         move[1] = '*';
5141         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5142         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5143     } else
5144     if(
5145        move[0]>='a' && move[0]<='x' &&
5146        move[3]>='0' && move[3]<='9' &&
5147        move[2]>='a' && move[2]<='x'    ) {
5148          /* output move, normal -> Shogi */
5149         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5150         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5151         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5152         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5153         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5154     }
5155     if (appData.debugMode) {
5156         fprintf(debugFP, "   out = '%s'\n", move);
5157     }
5158 }
5159
5160 char yy_textstr[8000];
5161
5162 /* Parser for moves from gnuchess, ICS, or user typein box */
5163 Boolean
5164 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5165      char *move;
5166      int moveNum;
5167      ChessMove *moveType;
5168      int *fromX, *fromY, *toX, *toY;
5169      char *promoChar;
5170 {
5171     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5172
5173     switch (*moveType) {
5174       case WhitePromotion:
5175       case BlackPromotion:
5176       case WhiteNonPromotion:
5177       case BlackNonPromotion:
5178       case NormalMove:
5179       case WhiteCapturesEnPassant:
5180       case BlackCapturesEnPassant:
5181       case WhiteKingSideCastle:
5182       case WhiteQueenSideCastle:
5183       case BlackKingSideCastle:
5184       case BlackQueenSideCastle:
5185       case WhiteKingSideCastleWild:
5186       case WhiteQueenSideCastleWild:
5187       case BlackKingSideCastleWild:
5188       case BlackQueenSideCastleWild:
5189       /* Code added by Tord: */
5190       case WhiteHSideCastleFR:
5191       case WhiteASideCastleFR:
5192       case BlackHSideCastleFR:
5193       case BlackASideCastleFR:
5194       /* End of code added by Tord */
5195       case IllegalMove:         /* bug or odd chess variant */
5196         *fromX = currentMoveString[0] - AAA;
5197         *fromY = currentMoveString[1] - ONE;
5198         *toX = currentMoveString[2] - AAA;
5199         *toY = currentMoveString[3] - ONE;
5200         *promoChar = currentMoveString[4];
5201         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5202             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5205     }
5206             *fromX = *fromY = *toX = *toY = 0;
5207             return FALSE;
5208         }
5209         if (appData.testLegality) {
5210           return (*moveType != IllegalMove);
5211         } else {
5212           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5213                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5214         }
5215
5216       case WhiteDrop:
5217       case BlackDrop:
5218         *fromX = *moveType == WhiteDrop ?
5219           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5220           (int) CharToPiece(ToLower(currentMoveString[0]));
5221         *fromY = DROP_RANK;
5222         *toX = currentMoveString[2] - AAA;
5223         *toY = currentMoveString[3] - ONE;
5224         *promoChar = NULLCHAR;
5225         return TRUE;
5226
5227       case AmbiguousMove:
5228       case ImpossibleMove:
5229       case EndOfFile:
5230       case ElapsedTime:
5231       case Comment:
5232       case PGNTag:
5233       case NAG:
5234       case WhiteWins:
5235       case BlackWins:
5236       case GameIsDrawn:
5237       default:
5238     if (appData.debugMode) {
5239         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5240     }
5241         /* bug? */
5242         *fromX = *fromY = *toX = *toY = 0;
5243         *promoChar = NULLCHAR;
5244         return FALSE;
5245     }
5246 }
5247
5248 Boolean pushed = FALSE;
5249
5250 void
5251 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5252 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5253   int fromX, fromY, toX, toY; char promoChar;
5254   ChessMove moveType;
5255   Boolean valid;
5256   int nr = 0;
5257
5258   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5259     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5260     pushed = TRUE;
5261   }
5262   endPV = forwardMostMove;
5263   do {
5264     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5265     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5266     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5267 if(appData.debugMode){
5268 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);
5269 }
5270     if(!valid && nr == 0 &&
5271        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5272         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5273         // Hande case where played move is different from leading PV move
5274         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5275         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5276         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5277         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5278           endPV += 2; // if position different, keep this
5279           moveList[endPV-1][0] = fromX + AAA;
5280           moveList[endPV-1][1] = fromY + ONE;
5281           moveList[endPV-1][2] = toX + AAA;
5282           moveList[endPV-1][3] = toY + ONE;
5283           parseList[endPV-1][0] = NULLCHAR;
5284           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5285         }
5286       }
5287     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5288     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5289     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5290     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5291         valid++; // allow comments in PV
5292         continue;
5293     }
5294     nr++;
5295     if(endPV+1 > framePtr) break; // no space, truncate
5296     if(!valid) break;
5297     endPV++;
5298     CopyBoard(boards[endPV], boards[endPV-1]);
5299     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5300     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5301     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5302     CoordsToAlgebraic(boards[endPV - 1],
5303                              PosFlags(endPV - 1),
5304                              fromY, fromX, toY, toX, promoChar,
5305                              parseList[endPV - 1]);
5306   } while(valid);
5307   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5308   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5309   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5310                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5311   DrawPosition(TRUE, boards[currentMove]);
5312 }
5313
5314 int
5315 MultiPV(ChessProgramState *cps)
5316 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5317         int i;
5318         for(i=0; i<cps->nrOptions; i++)
5319             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5320                 return i;
5321         return -1;
5322 }
5323
5324 Boolean
5325 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5326 {
5327         int startPV, multi, lineStart, origIndex = index;
5328         char *p, buf2[MSG_SIZ];
5329
5330         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5331         lastX = x; lastY = y;
5332         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5333         lineStart = startPV = index;
5334         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5335         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5336         index = startPV;
5337         do{ while(buf[index] && buf[index] != '\n') index++;
5338         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5339         buf[index] = 0;
5340         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5341                 int n = first.option[multi].value;
5342                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5343                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5344                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5345                 first.option[multi].value = n;
5346                 *start = *end = 0;
5347                 return FALSE;
5348         }
5349         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5350         *start = startPV; *end = index-1;
5351         return TRUE;
5352 }
5353
5354 Boolean
5355 LoadPV(int x, int y)
5356 { // called on right mouse click to load PV
5357   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5358   lastX = x; lastY = y;
5359   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5360   return TRUE;
5361 }
5362
5363 void
5364 UnLoadPV()
5365 {
5366   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5367   if(endPV < 0) return;
5368   endPV = -1;
5369   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5370         Boolean saveAnimate = appData.animate;
5371         if(pushed) {
5372             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5373                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5374             } else storedGames--; // abandon shelved tail of original game
5375         }
5376         pushed = FALSE;
5377         forwardMostMove = currentMove;
5378         currentMove = oldFMM;
5379         appData.animate = FALSE;
5380         ToNrEvent(forwardMostMove);
5381         appData.animate = saveAnimate;
5382   }
5383   currentMove = forwardMostMove;
5384   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5385   ClearPremoveHighlights();
5386   DrawPosition(TRUE, boards[currentMove]);
5387 }
5388
5389 void
5390 MovePV(int x, int y, int h)
5391 { // step through PV based on mouse coordinates (called on mouse move)
5392   int margin = h>>3, step = 0;
5393
5394   // we must somehow check if right button is still down (might be released off board!)
5395   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5396   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5397   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5398   if(!step) return;
5399   lastX = x; lastY = y;
5400
5401   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5402   if(endPV < 0) return;
5403   if(y < margin) step = 1; else
5404   if(y > h - margin) step = -1;
5405   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5406   currentMove += step;
5407   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5408   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5409                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5410   DrawPosition(FALSE, boards[currentMove]);
5411 }
5412
5413
5414 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5415 // All positions will have equal probability, but the current method will not provide a unique
5416 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5417 #define DARK 1
5418 #define LITE 2
5419 #define ANY 3
5420
5421 int squaresLeft[4];
5422 int piecesLeft[(int)BlackPawn];
5423 int seed, nrOfShuffles;
5424
5425 void GetPositionNumber()
5426 {       // sets global variable seed
5427         int i;
5428
5429         seed = appData.defaultFrcPosition;
5430         if(seed < 0) { // randomize based on time for negative FRC position numbers
5431                 for(i=0; i<50; i++) seed += random();
5432                 seed = random() ^ random() >> 8 ^ random() << 8;
5433                 if(seed<0) seed = -seed;
5434         }
5435 }
5436
5437 int put(Board board, int pieceType, int rank, int n, int shade)
5438 // put the piece on the (n-1)-th empty squares of the given shade
5439 {
5440         int i;
5441
5442         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5443                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5444                         board[rank][i] = (ChessSquare) pieceType;
5445                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5446                         squaresLeft[ANY]--;
5447                         piecesLeft[pieceType]--;
5448                         return i;
5449                 }
5450         }
5451         return -1;
5452 }
5453
5454
5455 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5456 // calculate where the next piece goes, (any empty square), and put it there
5457 {
5458         int i;
5459
5460         i = seed % squaresLeft[shade];
5461         nrOfShuffles *= squaresLeft[shade];
5462         seed /= squaresLeft[shade];
5463         put(board, pieceType, rank, i, shade);
5464 }
5465
5466 void AddTwoPieces(Board board, int pieceType, int rank)
5467 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5468 {
5469         int i, n=squaresLeft[ANY], j=n-1, k;
5470
5471         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5472         i = seed % k;  // pick one
5473         nrOfShuffles *= k;
5474         seed /= k;
5475         while(i >= j) i -= j--;
5476         j = n - 1 - j; i += j;
5477         put(board, pieceType, rank, j, ANY);
5478         put(board, pieceType, rank, i, ANY);
5479 }
5480
5481 void SetUpShuffle(Board board, int number)
5482 {
5483         int i, p, first=1;
5484
5485         GetPositionNumber(); nrOfShuffles = 1;
5486
5487         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5488         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5489         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5490
5491         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5492
5493         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5494             p = (int) board[0][i];
5495             if(p < (int) BlackPawn) piecesLeft[p] ++;
5496             board[0][i] = EmptySquare;
5497         }
5498
5499         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5500             // shuffles restricted to allow normal castling put KRR first
5501             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5502                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5503             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5504                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5505             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5506                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5507             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5508                 put(board, WhiteRook, 0, 0, ANY);
5509             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5510         }
5511
5512         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5513             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5514             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5515                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5516                 while(piecesLeft[p] >= 2) {
5517                     AddOnePiece(board, p, 0, LITE);
5518                     AddOnePiece(board, p, 0, DARK);
5519                 }
5520                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5521             }
5522
5523         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5524             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5525             // but we leave King and Rooks for last, to possibly obey FRC restriction
5526             if(p == (int)WhiteRook) continue;
5527             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5528             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5529         }
5530
5531         // now everything is placed, except perhaps King (Unicorn) and Rooks
5532
5533         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5534             // Last King gets castling rights
5535             while(piecesLeft[(int)WhiteUnicorn]) {
5536                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5537                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5538             }
5539
5540             while(piecesLeft[(int)WhiteKing]) {
5541                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5542                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5543             }
5544
5545
5546         } else {
5547             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5548             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5549         }
5550
5551         // Only Rooks can be left; simply place them all
5552         while(piecesLeft[(int)WhiteRook]) {
5553                 i = put(board, WhiteRook, 0, 0, ANY);
5554                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5555                         if(first) {
5556                                 first=0;
5557                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5558                         }
5559                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5560                 }
5561         }
5562         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5563             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5564         }
5565
5566         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5567 }
5568
5569 int SetCharTable( char *table, const char * map )
5570 /* [HGM] moved here from winboard.c because of its general usefulness */
5571 /*       Basically a safe strcpy that uses the last character as King */
5572 {
5573     int result = FALSE; int NrPieces;
5574
5575     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5576                     && NrPieces >= 12 && !(NrPieces&1)) {
5577         int i; /* [HGM] Accept even length from 12 to 34 */
5578
5579         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5580         for( i=0; i<NrPieces/2-1; i++ ) {
5581             table[i] = map[i];
5582             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5583         }
5584         table[(int) WhiteKing]  = map[NrPieces/2-1];
5585         table[(int) BlackKing]  = map[NrPieces-1];
5586
5587         result = TRUE;
5588     }
5589
5590     return result;
5591 }
5592
5593 void Prelude(Board board)
5594 {       // [HGM] superchess: random selection of exo-pieces
5595         int i, j, k; ChessSquare p;
5596         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5597
5598         GetPositionNumber(); // use FRC position number
5599
5600         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5601             SetCharTable(pieceToChar, appData.pieceToCharTable);
5602             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5603                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5604         }
5605
5606         j = seed%4;                 seed /= 4;
5607         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5608         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5609         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5610         j = seed%3 + (seed%3 >= j); seed /= 3;
5611         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5612         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5613         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5614         j = seed%3;                 seed /= 3;
5615         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5616         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5617         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5618         j = seed%2 + (seed%2 >= j); seed /= 2;
5619         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5620         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5621         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5622         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5623         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5624         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5625         put(board, exoPieces[0],    0, 0, ANY);
5626         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5627 }
5628
5629 void
5630 InitPosition(redraw)
5631      int redraw;
5632 {
5633     ChessSquare (* pieces)[BOARD_FILES];
5634     int i, j, pawnRow, overrule,
5635     oldx = gameInfo.boardWidth,
5636     oldy = gameInfo.boardHeight,
5637     oldh = gameInfo.holdingsWidth;
5638     static int oldv;
5639
5640     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5641
5642     /* [AS] Initialize pv info list [HGM] and game status */
5643     {
5644         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5645             pvInfoList[i].depth = 0;
5646             boards[i][EP_STATUS] = EP_NONE;
5647             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5648         }
5649
5650         initialRulePlies = 0; /* 50-move counter start */
5651
5652         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5653         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5654     }
5655
5656
5657     /* [HGM] logic here is completely changed. In stead of full positions */
5658     /* the initialized data only consist of the two backranks. The switch */
5659     /* selects which one we will use, which is than copied to the Board   */
5660     /* initialPosition, which for the rest is initialized by Pawns and    */
5661     /* empty squares. This initial position is then copied to boards[0],  */
5662     /* possibly after shuffling, so that it remains available.            */
5663
5664     gameInfo.holdingsWidth = 0; /* default board sizes */
5665     gameInfo.boardWidth    = 8;
5666     gameInfo.boardHeight   = 8;
5667     gameInfo.holdingsSize  = 0;
5668     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5669     for(i=0; i<BOARD_FILES-2; i++)
5670       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5671     initialPosition[EP_STATUS] = EP_NONE;
5672     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5673     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5674          SetCharTable(pieceNickName, appData.pieceNickNames);
5675     else SetCharTable(pieceNickName, "............");
5676     pieces = FIDEArray;
5677
5678     switch (gameInfo.variant) {
5679     case VariantFischeRandom:
5680       shuffleOpenings = TRUE;
5681     default:
5682       break;
5683     case VariantShatranj:
5684       pieces = ShatranjArray;
5685       nrCastlingRights = 0;
5686       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5687       break;
5688     case VariantMakruk:
5689       pieces = makrukArray;
5690       nrCastlingRights = 0;
5691       startedFromSetupPosition = TRUE;
5692       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5693       break;
5694     case VariantTwoKings:
5695       pieces = twoKingsArray;
5696       break;
5697     case VariantCapaRandom:
5698       shuffleOpenings = TRUE;
5699     case VariantCapablanca:
5700       pieces = CapablancaArray;
5701       gameInfo.boardWidth = 10;
5702       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5703       break;
5704     case VariantGothic:
5705       pieces = GothicArray;
5706       gameInfo.boardWidth = 10;
5707       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5708       break;
5709     case VariantSChess:
5710       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5711       gameInfo.holdingsSize = 7;
5712       break;
5713     case VariantJanus:
5714       pieces = JanusArray;
5715       gameInfo.boardWidth = 10;
5716       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5717       nrCastlingRights = 6;
5718         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5719         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5720         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5721         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5722         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5723         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5724       break;
5725     case VariantFalcon:
5726       pieces = FalconArray;
5727       gameInfo.boardWidth = 10;
5728       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5729       break;
5730     case VariantXiangqi:
5731       pieces = XiangqiArray;
5732       gameInfo.boardWidth  = 9;
5733       gameInfo.boardHeight = 10;
5734       nrCastlingRights = 0;
5735       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5736       break;
5737     case VariantShogi:
5738       pieces = ShogiArray;
5739       gameInfo.boardWidth  = 9;
5740       gameInfo.boardHeight = 9;
5741       gameInfo.holdingsSize = 7;
5742       nrCastlingRights = 0;
5743       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5744       break;
5745     case VariantCourier:
5746       pieces = CourierArray;
5747       gameInfo.boardWidth  = 12;
5748       nrCastlingRights = 0;
5749       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5750       break;
5751     case VariantKnightmate:
5752       pieces = KnightmateArray;
5753       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5754       break;
5755     case VariantSpartan:
5756       pieces = SpartanArray;
5757       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5758       break;
5759     case VariantFairy:
5760       pieces = fairyArray;
5761       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5762       break;
5763     case VariantGreat:
5764       pieces = GreatArray;
5765       gameInfo.boardWidth = 10;
5766       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5767       gameInfo.holdingsSize = 8;
5768       break;
5769     case VariantSuper:
5770       pieces = FIDEArray;
5771       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5772       gameInfo.holdingsSize = 8;
5773       startedFromSetupPosition = TRUE;
5774       break;
5775     case VariantCrazyhouse:
5776     case VariantBughouse:
5777       pieces = FIDEArray;
5778       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5779       gameInfo.holdingsSize = 5;
5780       break;
5781     case VariantWildCastle:
5782       pieces = FIDEArray;
5783       /* !!?shuffle with kings guaranteed to be on d or e file */
5784       shuffleOpenings = 1;
5785       break;
5786     case VariantNoCastle:
5787       pieces = FIDEArray;
5788       nrCastlingRights = 0;
5789       /* !!?unconstrained back-rank shuffle */
5790       shuffleOpenings = 1;
5791       break;
5792     }
5793
5794     overrule = 0;
5795     if(appData.NrFiles >= 0) {
5796         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5797         gameInfo.boardWidth = appData.NrFiles;
5798     }
5799     if(appData.NrRanks >= 0) {
5800         gameInfo.boardHeight = appData.NrRanks;
5801     }
5802     if(appData.holdingsSize >= 0) {
5803         i = appData.holdingsSize;
5804         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5805         gameInfo.holdingsSize = i;
5806     }
5807     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5808     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5809         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5810
5811     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5812     if(pawnRow < 1) pawnRow = 1;
5813     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5814
5815     /* User pieceToChar list overrules defaults */
5816     if(appData.pieceToCharTable != NULL)
5817         SetCharTable(pieceToChar, appData.pieceToCharTable);
5818
5819     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5820
5821         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5822             s = (ChessSquare) 0; /* account holding counts in guard band */
5823         for( i=0; i<BOARD_HEIGHT; i++ )
5824             initialPosition[i][j] = s;
5825
5826         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5827         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5828         initialPosition[pawnRow][j] = WhitePawn;
5829         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5830         if(gameInfo.variant == VariantXiangqi) {
5831             if(j&1) {
5832                 initialPosition[pawnRow][j] =
5833                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5834                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5835                    initialPosition[2][j] = WhiteCannon;
5836                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5837                 }
5838             }
5839         }
5840         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5841     }
5842     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5843
5844             j=BOARD_LEFT+1;
5845             initialPosition[1][j] = WhiteBishop;
5846             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5847             j=BOARD_RGHT-2;
5848             initialPosition[1][j] = WhiteRook;
5849             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5850     }
5851
5852     if( nrCastlingRights == -1) {
5853         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5854         /*       This sets default castling rights from none to normal corners   */
5855         /* Variants with other castling rights must set them themselves above    */
5856         nrCastlingRights = 6;
5857
5858         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5859         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5860         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5861         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5862         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5863         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5864      }
5865
5866      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5867      if(gameInfo.variant == VariantGreat) { // promotion commoners
5868         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5869         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5870         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5871         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5872      }
5873      if( gameInfo.variant == VariantSChess ) {
5874       initialPosition[1][0] = BlackMarshall;
5875       initialPosition[2][0] = BlackAngel;
5876       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5877       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5878       initialPosition[1][1] = initialPosition[2][1] = 
5879       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5880      }
5881   if (appData.debugMode) {
5882     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5883   }
5884     if(shuffleOpenings) {
5885         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5886         startedFromSetupPosition = TRUE;
5887     }
5888     if(startedFromPositionFile) {
5889       /* [HGM] loadPos: use PositionFile for every new game */
5890       CopyBoard(initialPosition, filePosition);
5891       for(i=0; i<nrCastlingRights; i++)
5892           initialRights[i] = filePosition[CASTLING][i];
5893       startedFromSetupPosition = TRUE;
5894     }
5895
5896     CopyBoard(boards[0], initialPosition);
5897
5898     if(oldx != gameInfo.boardWidth ||
5899        oldy != gameInfo.boardHeight ||
5900        oldv != gameInfo.variant ||
5901        oldh != gameInfo.holdingsWidth
5902                                          )
5903             InitDrawingSizes(-2 ,0);
5904
5905     oldv = gameInfo.variant;
5906     if (redraw)
5907       DrawPosition(TRUE, boards[currentMove]);
5908 }
5909
5910 void
5911 SendBoard(cps, moveNum)
5912      ChessProgramState *cps;
5913      int moveNum;
5914 {
5915     char message[MSG_SIZ];
5916
5917     if (cps->useSetboard) {
5918       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5919       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5920       SendToProgram(message, cps);
5921       free(fen);
5922
5923     } else {
5924       ChessSquare *bp;
5925       int i, j;
5926       /* Kludge to set black to move, avoiding the troublesome and now
5927        * deprecated "black" command.
5928        */
5929       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5930         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5931
5932       SendToProgram("edit\n", cps);
5933       SendToProgram("#\n", cps);
5934       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5935         bp = &boards[moveNum][i][BOARD_LEFT];
5936         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5937           if ((int) *bp < (int) BlackPawn) {
5938             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5939                     AAA + j, ONE + i);
5940             if(message[0] == '+' || message[0] == '~') {
5941               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5942                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5943                         AAA + j, ONE + i);
5944             }
5945             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5946                 message[1] = BOARD_RGHT   - 1 - j + '1';
5947                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5948             }
5949             SendToProgram(message, cps);
5950           }
5951         }
5952       }
5953
5954       SendToProgram("c\n", cps);
5955       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5956         bp = &boards[moveNum][i][BOARD_LEFT];
5957         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5958           if (((int) *bp != (int) EmptySquare)
5959               && ((int) *bp >= (int) BlackPawn)) {
5960             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5961                     AAA + j, ONE + i);
5962             if(message[0] == '+' || message[0] == '~') {
5963               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5964                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5965                         AAA + j, ONE + i);
5966             }
5967             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5968                 message[1] = BOARD_RGHT   - 1 - j + '1';
5969                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5970             }
5971             SendToProgram(message, cps);
5972           }
5973         }
5974       }
5975
5976       SendToProgram(".\n", cps);
5977     }
5978     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5979 }
5980
5981 ChessSquare
5982 DefaultPromoChoice(int white)
5983 {
5984     ChessSquare result;
5985     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5986         result = WhiteFerz; // no choice
5987     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5988         result= WhiteKing; // in Suicide Q is the last thing we want
5989     else if(gameInfo.variant == VariantSpartan)
5990         result = white ? WhiteQueen : WhiteAngel;
5991     else result = WhiteQueen;
5992     if(!white) result = WHITE_TO_BLACK result;
5993     return result;
5994 }
5995
5996 static int autoQueen; // [HGM] oneclick
5997
5998 int
5999 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6000 {
6001     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6002     /* [HGM] add Shogi promotions */
6003     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6004     ChessSquare piece;
6005     ChessMove moveType;
6006     Boolean premove;
6007
6008     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6009     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6010
6011     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6012       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6013         return FALSE;
6014
6015     piece = boards[currentMove][fromY][fromX];
6016     if(gameInfo.variant == VariantShogi) {
6017         promotionZoneSize = BOARD_HEIGHT/3;
6018         highestPromotingPiece = (int)WhiteFerz;
6019     } else if(gameInfo.variant == VariantMakruk) {
6020         promotionZoneSize = 3;
6021     }
6022
6023     // Treat Lance as Pawn when it is not representing Amazon
6024     if(gameInfo.variant != VariantSuper) {
6025         if(piece == WhiteLance) piece = WhitePawn; else
6026         if(piece == BlackLance) piece = BlackPawn;
6027     }
6028
6029     // next weed out all moves that do not touch the promotion zone at all
6030     if((int)piece >= BlackPawn) {
6031         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6032              return FALSE;
6033         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6034     } else {
6035         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6036            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6037     }
6038
6039     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6040
6041     // weed out mandatory Shogi promotions
6042     if(gameInfo.variant == VariantShogi) {
6043         if(piece >= BlackPawn) {
6044             if(toY == 0 && piece == BlackPawn ||
6045                toY == 0 && piece == BlackQueen ||
6046                toY <= 1 && piece == BlackKnight) {
6047                 *promoChoice = '+';
6048                 return FALSE;
6049             }
6050         } else {
6051             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6052                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6053                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6054                 *promoChoice = '+';
6055                 return FALSE;
6056             }
6057         }
6058     }
6059
6060     // weed out obviously illegal Pawn moves
6061     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6062         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6063         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6064         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6065         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6066         // note we are not allowed to test for valid (non-)capture, due to premove
6067     }
6068
6069     // we either have a choice what to promote to, or (in Shogi) whether to promote
6070     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6071         *promoChoice = PieceToChar(BlackFerz);  // no choice
6072         return FALSE;
6073     }
6074     // no sense asking what we must promote to if it is going to explode...
6075     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6076         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6077         return FALSE;
6078     }
6079     // give caller the default choice even if we will not make it
6080     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6081     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6082     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6083                            && gameInfo.variant != VariantShogi
6084                            && gameInfo.variant != VariantSuper) return FALSE;
6085     if(autoQueen) return FALSE; // predetermined
6086
6087     // suppress promotion popup on illegal moves that are not premoves
6088     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6089               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6090     if(appData.testLegality && !premove) {
6091         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6092                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6093         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6094             return FALSE;
6095     }
6096
6097     return TRUE;
6098 }
6099
6100 int
6101 InPalace(row, column)
6102      int row, column;
6103 {   /* [HGM] for Xiangqi */
6104     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6105          column < (BOARD_WIDTH + 4)/2 &&
6106          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6107     return FALSE;
6108 }
6109
6110 int
6111 PieceForSquare (x, y)
6112      int x;
6113      int y;
6114 {
6115   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6116      return -1;
6117   else
6118      return boards[currentMove][y][x];
6119 }
6120
6121 int
6122 OKToStartUserMove(x, y)
6123      int x, y;
6124 {
6125     ChessSquare from_piece;
6126     int white_piece;
6127
6128     if (matchMode) return FALSE;
6129     if (gameMode == EditPosition) return TRUE;
6130
6131     if (x >= 0 && y >= 0)
6132       from_piece = boards[currentMove][y][x];
6133     else
6134       from_piece = EmptySquare;
6135
6136     if (from_piece == EmptySquare) return FALSE;
6137
6138     white_piece = (int)from_piece >= (int)WhitePawn &&
6139       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6140
6141     switch (gameMode) {
6142       case PlayFromGameFile:
6143       case AnalyzeFile:
6144       case TwoMachinesPlay:
6145       case EndOfGame:
6146         return FALSE;
6147
6148       case IcsObserving:
6149       case IcsIdle:
6150         return FALSE;
6151
6152       case MachinePlaysWhite:
6153       case IcsPlayingBlack:
6154         if (appData.zippyPlay) return FALSE;
6155         if (white_piece) {
6156             DisplayMoveError(_("You are playing Black"));
6157             return FALSE;
6158         }
6159         break;
6160
6161       case MachinePlaysBlack:
6162       case IcsPlayingWhite:
6163         if (appData.zippyPlay) return FALSE;
6164         if (!white_piece) {
6165             DisplayMoveError(_("You are playing White"));
6166             return FALSE;
6167         }
6168         break;
6169
6170       case EditGame:
6171         if (!white_piece && WhiteOnMove(currentMove)) {
6172             DisplayMoveError(_("It is White's turn"));
6173             return FALSE;
6174         }
6175         if (white_piece && !WhiteOnMove(currentMove)) {
6176             DisplayMoveError(_("It is Black's turn"));
6177             return FALSE;
6178         }
6179         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6180             /* Editing correspondence game history */
6181             /* Could disallow this or prompt for confirmation */
6182             cmailOldMove = -1;
6183         }
6184         break;
6185
6186       case BeginningOfGame:
6187         if (appData.icsActive) return FALSE;
6188         if (!appData.noChessProgram) {
6189             if (!white_piece) {
6190                 DisplayMoveError(_("You are playing White"));
6191                 return FALSE;
6192             }
6193         }
6194         break;
6195
6196       case Training:
6197         if (!white_piece && WhiteOnMove(currentMove)) {
6198             DisplayMoveError(_("It is White's turn"));
6199             return FALSE;
6200         }
6201         if (white_piece && !WhiteOnMove(currentMove)) {
6202             DisplayMoveError(_("It is Black's turn"));
6203             return FALSE;
6204         }
6205         break;
6206
6207       default:
6208       case IcsExamining:
6209         break;
6210     }
6211     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6212         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6213         && gameMode != AnalyzeFile && gameMode != Training) {
6214         DisplayMoveError(_("Displayed position is not current"));
6215         return FALSE;
6216     }
6217     return TRUE;
6218 }
6219
6220 Boolean
6221 OnlyMove(int *x, int *y, Boolean captures) {
6222     DisambiguateClosure cl;
6223     if (appData.zippyPlay) return FALSE;
6224     switch(gameMode) {
6225       case MachinePlaysBlack:
6226       case IcsPlayingWhite:
6227       case BeginningOfGame:
6228         if(!WhiteOnMove(currentMove)) return FALSE;
6229         break;
6230       case MachinePlaysWhite:
6231       case IcsPlayingBlack:
6232         if(WhiteOnMove(currentMove)) return FALSE;
6233         break;
6234       case EditGame:
6235         break;
6236       default:
6237         return FALSE;
6238     }
6239     cl.pieceIn = EmptySquare;
6240     cl.rfIn = *y;
6241     cl.ffIn = *x;
6242     cl.rtIn = -1;
6243     cl.ftIn = -1;
6244     cl.promoCharIn = NULLCHAR;
6245     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6246     if( cl.kind == NormalMove ||
6247         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6248         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6249         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6250       fromX = cl.ff;
6251       fromY = cl.rf;
6252       *x = cl.ft;
6253       *y = cl.rt;
6254       return TRUE;
6255     }
6256     if(cl.kind != ImpossibleMove) return FALSE;
6257     cl.pieceIn = EmptySquare;
6258     cl.rfIn = -1;
6259     cl.ffIn = -1;
6260     cl.rtIn = *y;
6261     cl.ftIn = *x;
6262     cl.promoCharIn = NULLCHAR;
6263     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6264     if( cl.kind == NormalMove ||
6265         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6266         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6267         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6268       fromX = cl.ff;
6269       fromY = cl.rf;
6270       *x = cl.ft;
6271       *y = cl.rt;
6272       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6273       return TRUE;
6274     }
6275     return FALSE;
6276 }
6277
6278 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6279 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6280 int lastLoadGameUseList = FALSE;
6281 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6282 ChessMove lastLoadGameStart = EndOfFile;
6283
6284 void
6285 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6286      int fromX, fromY, toX, toY;
6287      int promoChar;
6288 {
6289     ChessMove moveType;
6290     ChessSquare pdown, pup;
6291
6292     /* Check if the user is playing in turn.  This is complicated because we
6293        let the user "pick up" a piece before it is his turn.  So the piece he
6294        tried to pick up may have been captured by the time he puts it down!
6295        Therefore we use the color the user is supposed to be playing in this
6296        test, not the color of the piece that is currently on the starting
6297        square---except in EditGame mode, where the user is playing both
6298        sides; fortunately there the capture race can't happen.  (It can
6299        now happen in IcsExamining mode, but that's just too bad.  The user
6300        will get a somewhat confusing message in that case.)
6301        */
6302
6303     switch (gameMode) {
6304       case PlayFromGameFile:
6305       case AnalyzeFile:
6306       case TwoMachinesPlay:
6307       case EndOfGame:
6308       case IcsObserving:
6309       case IcsIdle:
6310         /* We switched into a game mode where moves are not accepted,
6311            perhaps while the mouse button was down. */
6312         return;
6313
6314       case MachinePlaysWhite:
6315         /* User is moving for Black */
6316         if (WhiteOnMove(currentMove)) {
6317             DisplayMoveError(_("It is White's turn"));
6318             return;
6319         }
6320         break;
6321
6322       case MachinePlaysBlack:
6323         /* User is moving for White */
6324         if (!WhiteOnMove(currentMove)) {
6325             DisplayMoveError(_("It is Black's turn"));
6326             return;
6327         }
6328         break;
6329
6330       case EditGame:
6331       case IcsExamining:
6332       case BeginningOfGame:
6333       case AnalyzeMode:
6334       case Training:
6335         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6336         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6337             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6338             /* User is moving for Black */
6339             if (WhiteOnMove(currentMove)) {
6340                 DisplayMoveError(_("It is White's turn"));
6341                 return;
6342             }
6343         } else {
6344             /* User is moving for White */
6345             if (!WhiteOnMove(currentMove)) {
6346                 DisplayMoveError(_("It is Black's turn"));
6347                 return;
6348             }
6349         }
6350         break;
6351
6352       case IcsPlayingBlack:
6353         /* User is moving for Black */
6354         if (WhiteOnMove(currentMove)) {
6355             if (!appData.premove) {
6356                 DisplayMoveError(_("It is White's turn"));
6357             } else if (toX >= 0 && toY >= 0) {
6358                 premoveToX = toX;
6359                 premoveToY = toY;
6360                 premoveFromX = fromX;
6361                 premoveFromY = fromY;
6362                 premovePromoChar = promoChar;
6363                 gotPremove = 1;
6364                 if (appData.debugMode)
6365                     fprintf(debugFP, "Got premove: fromX %d,"
6366                             "fromY %d, toX %d, toY %d\n",
6367                             fromX, fromY, toX, toY);
6368             }
6369             return;
6370         }
6371         break;
6372
6373       case IcsPlayingWhite:
6374         /* User is moving for White */
6375         if (!WhiteOnMove(currentMove)) {
6376             if (!appData.premove) {
6377                 DisplayMoveError(_("It is Black's turn"));
6378             } else if (toX >= 0 && toY >= 0) {
6379                 premoveToX = toX;
6380                 premoveToY = toY;
6381                 premoveFromX = fromX;
6382                 premoveFromY = fromY;
6383                 premovePromoChar = promoChar;
6384                 gotPremove = 1;
6385                 if (appData.debugMode)
6386                     fprintf(debugFP, "Got premove: fromX %d,"
6387                             "fromY %d, toX %d, toY %d\n",
6388                             fromX, fromY, toX, toY);
6389             }
6390             return;
6391         }
6392         break;
6393
6394       default:
6395         break;
6396
6397       case EditPosition:
6398         /* EditPosition, empty square, or different color piece;
6399            click-click move is possible */
6400         if (toX == -2 || toY == -2) {
6401             boards[0][fromY][fromX] = EmptySquare;
6402             DrawPosition(FALSE, boards[currentMove]);
6403             return;
6404         } else if (toX >= 0 && toY >= 0) {
6405             boards[0][toY][toX] = boards[0][fromY][fromX];
6406             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6407                 if(boards[0][fromY][0] != EmptySquare) {
6408                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6409                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6410                 }
6411             } else
6412             if(fromX == BOARD_RGHT+1) {
6413                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6414                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6415                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6416                 }
6417             } else
6418             boards[0][fromY][fromX] = EmptySquare;
6419             DrawPosition(FALSE, boards[currentMove]);
6420             return;
6421         }
6422         return;
6423     }
6424
6425     if(toX < 0 || toY < 0) return;
6426     pdown = boards[currentMove][fromY][fromX];
6427     pup = boards[currentMove][toY][toX];
6428
6429     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6430     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6431          if( pup != EmptySquare ) return;
6432          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6433            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6434                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6435            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6436            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6437            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6438            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6439          fromY = DROP_RANK;
6440     }
6441
6442     /* [HGM] always test for legality, to get promotion info */
6443     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6444                                          fromY, fromX, toY, toX, promoChar);
6445     /* [HGM] but possibly ignore an IllegalMove result */
6446     if (appData.testLegality) {
6447         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6448             DisplayMoveError(_("Illegal move"));
6449             return;
6450         }
6451     }
6452
6453     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6454 }
6455
6456 /* Common tail of UserMoveEvent and DropMenuEvent */
6457 int
6458 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6459      ChessMove moveType;
6460      int fromX, fromY, toX, toY;
6461      /*char*/int promoChar;
6462 {
6463     char *bookHit = 0;
6464
6465     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6466         // [HGM] superchess: suppress promotions to non-available piece
6467         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6468         if(WhiteOnMove(currentMove)) {
6469             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6470         } else {
6471             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6472         }
6473     }
6474
6475     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6476        move type in caller when we know the move is a legal promotion */
6477     if(moveType == NormalMove && promoChar)
6478         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6479
6480     /* [HGM] <popupFix> The following if has been moved here from
6481        UserMoveEvent(). Because it seemed to belong here (why not allow
6482        piece drops in training games?), and because it can only be
6483        performed after it is known to what we promote. */
6484     if (gameMode == Training) {
6485       /* compare the move played on the board to the next move in the
6486        * game. If they match, display the move and the opponent's response.
6487        * If they don't match, display an error message.
6488        */
6489       int saveAnimate;
6490       Board testBoard;
6491       CopyBoard(testBoard, boards[currentMove]);
6492       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6493
6494       if (CompareBoards(testBoard, boards[currentMove+1])) {
6495         ForwardInner(currentMove+1);
6496
6497         /* Autoplay the opponent's response.
6498          * if appData.animate was TRUE when Training mode was entered,
6499          * the response will be animated.
6500          */
6501         saveAnimate = appData.animate;
6502         appData.animate = animateTraining;
6503         ForwardInner(currentMove+1);
6504         appData.animate = saveAnimate;
6505
6506         /* check for the end of the game */
6507         if (currentMove >= forwardMostMove) {
6508           gameMode = PlayFromGameFile;
6509           ModeHighlight();
6510           SetTrainingModeOff();
6511           DisplayInformation(_("End of game"));
6512         }
6513       } else {
6514         DisplayError(_("Incorrect move"), 0);
6515       }
6516       return 1;
6517     }
6518
6519   /* Ok, now we know that the move is good, so we can kill
6520      the previous line in Analysis Mode */
6521   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6522                                 && currentMove < forwardMostMove) {
6523     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6524     else forwardMostMove = currentMove;
6525   }
6526
6527   /* If we need the chess program but it's dead, restart it */
6528   ResurrectChessProgram();
6529
6530   /* A user move restarts a paused game*/
6531   if (pausing)
6532     PauseEvent();
6533
6534   thinkOutput[0] = NULLCHAR;
6535
6536   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6537
6538   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6539     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6540     return 1;
6541   }
6542
6543   if (gameMode == BeginningOfGame) {
6544     if (appData.noChessProgram) {
6545       gameMode = EditGame;
6546       SetGameInfo();
6547     } else {
6548       char buf[MSG_SIZ];
6549       gameMode = MachinePlaysBlack;
6550       StartClocks();
6551       SetGameInfo();
6552       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6553       DisplayTitle(buf);
6554       if (first.sendName) {
6555         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6556         SendToProgram(buf, &first);
6557       }
6558       StartClocks();
6559     }
6560     ModeHighlight();
6561   }
6562
6563   /* Relay move to ICS or chess engine */
6564   if (appData.icsActive) {
6565     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6566         gameMode == IcsExamining) {
6567       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6568         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6569         SendToICS("draw ");
6570         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6571       }
6572       // also send plain move, in case ICS does not understand atomic claims
6573       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6574       ics_user_moved = 1;
6575     }
6576   } else {
6577     if (first.sendTime && (gameMode == BeginningOfGame ||
6578                            gameMode == MachinePlaysWhite ||
6579                            gameMode == MachinePlaysBlack)) {
6580       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6581     }
6582     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6583          // [HGM] book: if program might be playing, let it use book
6584         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6585         first.maybeThinking = TRUE;
6586     } else SendMoveToProgram(forwardMostMove-1, &first);
6587     if (currentMove == cmailOldMove + 1) {
6588       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6589     }
6590   }
6591
6592   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6593
6594   switch (gameMode) {
6595   case EditGame:
6596     if(appData.testLegality)
6597     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6598     case MT_NONE:
6599     case MT_CHECK:
6600       break;
6601     case MT_CHECKMATE:
6602     case MT_STAINMATE:
6603       if (WhiteOnMove(currentMove)) {
6604         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6605       } else {
6606         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6607       }
6608       break;
6609     case MT_STALEMATE:
6610       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6611       break;
6612     }
6613     break;
6614
6615   case MachinePlaysBlack:
6616   case MachinePlaysWhite:
6617     /* disable certain menu options while machine is thinking */
6618     SetMachineThinkingEnables();
6619     break;
6620
6621   default:
6622     break;
6623   }
6624
6625   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6626   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6627
6628   if(bookHit) { // [HGM] book: simulate book reply
6629         static char bookMove[MSG_SIZ]; // a bit generous?
6630
6631         programStats.nodes = programStats.depth = programStats.time =
6632         programStats.score = programStats.got_only_move = 0;
6633         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6634
6635         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6636         strcat(bookMove, bookHit);
6637         HandleMachineMove(bookMove, &first);
6638   }
6639   return 1;
6640 }
6641
6642 void
6643 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6644      Board board;
6645      int flags;
6646      ChessMove kind;
6647      int rf, ff, rt, ft;
6648      VOIDSTAR closure;
6649 {
6650     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6651     Markers *m = (Markers *) closure;
6652     if(rf == fromY && ff == fromX)
6653         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6654                          || kind == WhiteCapturesEnPassant
6655                          || kind == BlackCapturesEnPassant);
6656     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6657 }
6658
6659 void
6660 MarkTargetSquares(int clear)
6661 {
6662   int x, y;
6663   if(!appData.markers || !appData.highlightDragging ||
6664      !appData.testLegality || gameMode == EditPosition) return;
6665   if(clear) {
6666     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6667   } else {
6668     int capt = 0;
6669     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6670     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6671       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6672       if(capt)
6673       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6674     }
6675   }
6676   DrawPosition(TRUE, NULL);
6677 }
6678
6679 int
6680 Explode(Board board, int fromX, int fromY, int toX, int toY)
6681 {
6682     if(gameInfo.variant == VariantAtomic &&
6683        (board[toY][toX] != EmptySquare ||                     // capture?
6684         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6685                          board[fromY][fromX] == BlackPawn   )
6686       )) {
6687         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6688         return TRUE;
6689     }
6690     return FALSE;
6691 }
6692
6693 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6694
6695 int CanPromote(ChessSquare piece, int y)
6696 {
6697         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6698         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6699         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6700            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6701            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6702                                                   gameInfo.variant == VariantMakruk) return FALSE;
6703         return (piece == BlackPawn && y == 1 ||
6704                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6705                 piece == BlackLance && y == 1 ||
6706                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6707 }
6708
6709 void LeftClick(ClickType clickType, int xPix, int yPix)
6710 {
6711     int x, y;
6712     Boolean saveAnimate;
6713     static int second = 0, promotionChoice = 0, clearFlag = 0;
6714     char promoChoice = NULLCHAR;
6715     ChessSquare piece;
6716
6717     if(appData.seekGraph && appData.icsActive && loggedOn &&
6718         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6719         SeekGraphClick(clickType, xPix, yPix, 0);
6720         return;
6721     }
6722
6723     if (clickType == Press) ErrorPopDown();
6724     MarkTargetSquares(1);
6725
6726     x = EventToSquare(xPix, BOARD_WIDTH);
6727     y = EventToSquare(yPix, BOARD_HEIGHT);
6728     if (!flipView && y >= 0) {
6729         y = BOARD_HEIGHT - 1 - y;
6730     }
6731     if (flipView && x >= 0) {
6732         x = BOARD_WIDTH - 1 - x;
6733     }
6734
6735     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6736         defaultPromoChoice = promoSweep;
6737         promoSweep = EmptySquare;   // terminate sweep
6738         promoDefaultAltered = TRUE;
6739         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6740     }
6741
6742     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6743         if(clickType == Release) return; // ignore upclick of click-click destination
6744         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6745         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6746         if(gameInfo.holdingsWidth &&
6747                 (WhiteOnMove(currentMove)
6748                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6749                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6750             // click in right holdings, for determining promotion piece
6751             ChessSquare p = boards[currentMove][y][x];
6752             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6753             if(p != EmptySquare) {
6754                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6755                 fromX = fromY = -1;
6756                 return;
6757             }
6758         }
6759         DrawPosition(FALSE, boards[currentMove]);
6760         return;
6761     }
6762
6763     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6764     if(clickType == Press
6765             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6766               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6767               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6768         return;
6769
6770     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6771         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6772
6773     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6774         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6775                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6776         defaultPromoChoice = DefaultPromoChoice(side);
6777     }
6778
6779     autoQueen = appData.alwaysPromoteToQueen;
6780
6781     if (fromX == -1) {
6782       int originalY = y;
6783       gatingPiece = EmptySquare;
6784       if (clickType != Press) {
6785         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6786             DragPieceEnd(xPix, yPix); dragging = 0;
6787             DrawPosition(FALSE, NULL);
6788         }
6789         return;
6790       }
6791       fromX = x; fromY = y;
6792       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6793          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6794          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6795             /* First square */
6796             if (OKToStartUserMove(fromX, fromY)) {
6797                 second = 0;
6798                 MarkTargetSquares(0);
6799                 DragPieceBegin(xPix, yPix); dragging = 1;
6800                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6801                     promoSweep = defaultPromoChoice;
6802                     selectFlag = 0; lastX = xPix; lastY = yPix;
6803                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6804                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6805                 }
6806                 if (appData.highlightDragging) {
6807                     SetHighlights(fromX, fromY, -1, -1);
6808                 }
6809             } else fromX = fromY = -1;
6810             return;
6811         }
6812     }
6813
6814     /* fromX != -1 */
6815     if (clickType == Press && gameMode != EditPosition) {
6816         ChessSquare fromP;
6817         ChessSquare toP;
6818         int frc;
6819
6820         // ignore off-board to clicks
6821         if(y < 0 || x < 0) return;
6822
6823         /* Check if clicking again on the same color piece */
6824         fromP = boards[currentMove][fromY][fromX];
6825         toP = boards[currentMove][y][x];
6826         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6827         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6828              WhitePawn <= toP && toP <= WhiteKing &&
6829              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6830              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6831             (BlackPawn <= fromP && fromP <= BlackKing &&
6832              BlackPawn <= toP && toP <= BlackKing &&
6833              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6834              !(fromP == BlackKing && toP == BlackRook && frc))) {
6835             /* Clicked again on same color piece -- changed his mind */
6836             second = (x == fromX && y == fromY);
6837             promoDefaultAltered = FALSE;
6838            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6839             if (appData.highlightDragging) {
6840                 SetHighlights(x, y, -1, -1);
6841             } else {
6842                 ClearHighlights();
6843             }
6844             if (OKToStartUserMove(x, y)) {
6845                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6846                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6847                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6848                  gatingPiece = boards[currentMove][fromY][fromX];
6849                 else gatingPiece = EmptySquare;
6850                 fromX = x;
6851                 fromY = y; dragging = 1;
6852                 MarkTargetSquares(0);
6853                 DragPieceBegin(xPix, yPix);
6854                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6855                     promoSweep = defaultPromoChoice;
6856                     selectFlag = 0; lastX = xPix; lastY = yPix;
6857                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6858                 }
6859             }
6860            }
6861            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6862            second = FALSE; 
6863         }
6864         // ignore clicks on holdings
6865         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6866     }
6867
6868     if (clickType == Release && x == fromX && y == fromY) {
6869         DragPieceEnd(xPix, yPix); dragging = 0;
6870         if(clearFlag) {
6871             // a deferred attempt to click-click move an empty square on top of a piece
6872             boards[currentMove][y][x] = EmptySquare;
6873             ClearHighlights();
6874             DrawPosition(FALSE, boards[currentMove]);
6875             fromX = fromY = -1; clearFlag = 0;
6876             return;
6877         }
6878         if (appData.animateDragging) {
6879             /* Undo animation damage if any */
6880             DrawPosition(FALSE, NULL);
6881         }
6882         if (second) {
6883             /* Second up/down in same square; just abort move */
6884             second = 0;
6885             fromX = fromY = -1;
6886             gatingPiece = EmptySquare;
6887             ClearHighlights();
6888             gotPremove = 0;
6889             ClearPremoveHighlights();
6890         } else {
6891             /* First upclick in same square; start click-click mode */
6892             SetHighlights(x, y, -1, -1);
6893         }
6894         return;
6895     }
6896
6897     clearFlag = 0;
6898
6899     /* we now have a different from- and (possibly off-board) to-square */
6900     /* Completed move */
6901     toX = x;
6902     toY = y;
6903     saveAnimate = appData.animate;
6904     if (clickType == Press) {
6905         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6906             // must be Edit Position mode with empty-square selected
6907             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6908             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6909             return;
6910         }
6911         /* Finish clickclick move */
6912         if (appData.animate || appData.highlightLastMove) {
6913             SetHighlights(fromX, fromY, toX, toY);
6914         } else {
6915             ClearHighlights();
6916         }
6917     } else {
6918         /* Finish drag move */
6919         if (appData.highlightLastMove) {
6920             SetHighlights(fromX, fromY, toX, toY);
6921         } else {
6922             ClearHighlights();
6923         }
6924         DragPieceEnd(xPix, yPix); dragging = 0;
6925         /* Don't animate move and drag both */
6926         appData.animate = FALSE;
6927     }
6928
6929     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6930     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6931         ChessSquare piece = boards[currentMove][fromY][fromX];
6932         if(gameMode == EditPosition && piece != EmptySquare &&
6933            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6934             int n;
6935
6936             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6937                 n = PieceToNumber(piece - (int)BlackPawn);
6938                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6939                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6940                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6941             } else
6942             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6943                 n = PieceToNumber(piece);
6944                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6945                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6946                 boards[currentMove][n][BOARD_WIDTH-2]++;
6947             }
6948             boards[currentMove][fromY][fromX] = EmptySquare;
6949         }
6950         ClearHighlights();
6951         fromX = fromY = -1;
6952         DrawPosition(TRUE, boards[currentMove]);
6953         return;
6954     }
6955
6956     // off-board moves should not be highlighted
6957     if(x < 0 || y < 0) ClearHighlights();
6958
6959     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6960
6961     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6962         SetHighlights(fromX, fromY, toX, toY);
6963         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6964             // [HGM] super: promotion to captured piece selected from holdings
6965             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6966             promotionChoice = TRUE;
6967             // kludge follows to temporarily execute move on display, without promoting yet
6968             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6969             boards[currentMove][toY][toX] = p;
6970             DrawPosition(FALSE, boards[currentMove]);
6971             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6972             boards[currentMove][toY][toX] = q;
6973             DisplayMessage("Click in holdings to choose piece", "");
6974             return;
6975         }
6976         PromotionPopUp();
6977     } else {
6978         int oldMove = currentMove;
6979         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6980         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6981         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6982         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6983            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6984             DrawPosition(TRUE, boards[currentMove]);
6985         fromX = fromY = -1;
6986     }
6987     appData.animate = saveAnimate;
6988     if (appData.animate || appData.animateDragging) {
6989         /* Undo animation damage if needed */
6990         DrawPosition(FALSE, NULL);
6991     }
6992 }
6993
6994 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6995 {   // front-end-free part taken out of PieceMenuPopup
6996     int whichMenu; int xSqr, ySqr;
6997
6998     if(seekGraphUp) { // [HGM] seekgraph
6999         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7000         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7001         return -2;
7002     }
7003
7004     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7005          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7006         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7007         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7008         if(action == Press)   {
7009             originalFlip = flipView;
7010             flipView = !flipView; // temporarily flip board to see game from partners perspective
7011             DrawPosition(TRUE, partnerBoard);
7012             DisplayMessage(partnerStatus, "");
7013             partnerUp = TRUE;
7014         } else if(action == Release) {
7015             flipView = originalFlip;
7016             DrawPosition(TRUE, boards[currentMove]);
7017             partnerUp = FALSE;
7018         }
7019         return -2;
7020     }
7021
7022     xSqr = EventToSquare(x, BOARD_WIDTH);
7023     ySqr = EventToSquare(y, BOARD_HEIGHT);
7024     if (action == Release) {
7025         if(pieceSweep != EmptySquare) {
7026             EditPositionMenuEvent(pieceSweep, toX, toY);
7027             pieceSweep = EmptySquare;
7028         } else UnLoadPV(); // [HGM] pv
7029     }
7030     if (action != Press) return -2; // return code to be ignored
7031     switch (gameMode) {
7032       case IcsExamining:
7033         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7034       case EditPosition:
7035         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7036         if (xSqr < 0 || ySqr < 0) return -1;
7037         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7038         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7039         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7040         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7041         NextPiece(0);
7042         return -2;\r
7043       case IcsObserving:
7044         if(!appData.icsEngineAnalyze) return -1;
7045       case IcsPlayingWhite:
7046       case IcsPlayingBlack:
7047         if(!appData.zippyPlay) goto noZip;
7048       case AnalyzeMode:
7049       case AnalyzeFile:
7050       case MachinePlaysWhite:
7051       case MachinePlaysBlack:
7052       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7053         if (!appData.dropMenu) {
7054           LoadPV(x, y);
7055           return 2; // flag front-end to grab mouse events
7056         }
7057         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7058            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7059       case EditGame:
7060       noZip:
7061         if (xSqr < 0 || ySqr < 0) return -1;
7062         if (!appData.dropMenu || appData.testLegality &&
7063             gameInfo.variant != VariantBughouse &&
7064             gameInfo.variant != VariantCrazyhouse) return -1;
7065         whichMenu = 1; // drop menu
7066         break;
7067       default:
7068         return -1;
7069     }
7070
7071     if (((*fromX = xSqr) < 0) ||
7072         ((*fromY = ySqr) < 0)) {
7073         *fromX = *fromY = -1;
7074         return -1;
7075     }
7076     if (flipView)
7077       *fromX = BOARD_WIDTH - 1 - *fromX;
7078     else
7079       *fromY = BOARD_HEIGHT - 1 - *fromY;
7080
7081     return whichMenu;
7082 }
7083
7084 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7085 {
7086 //    char * hint = lastHint;
7087     FrontEndProgramStats stats;
7088
7089     stats.which = cps == &first ? 0 : 1;
7090     stats.depth = cpstats->depth;
7091     stats.nodes = cpstats->nodes;
7092     stats.score = cpstats->score;
7093     stats.time = cpstats->time;
7094     stats.pv = cpstats->movelist;
7095     stats.hint = lastHint;
7096     stats.an_move_index = 0;
7097     stats.an_move_count = 0;
7098
7099     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7100         stats.hint = cpstats->move_name;
7101         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7102         stats.an_move_count = cpstats->nr_moves;
7103     }
7104
7105     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
7106
7107     SetProgramStats( &stats );
7108 }
7109
7110 #define MAXPLAYERS 500
7111
7112 char *
7113 TourneyStandings(int display)
7114 {
7115     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7116     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7117     char result, *p, *names[MAXPLAYERS];
7118
7119     names[0] = p = strdup(appData.participants);
7120     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7121
7122     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7123
7124     while(result = appData.results[nr]) {
7125         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7126         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7127         wScore = bScore = 0;
7128         switch(result) {
7129           case '+': wScore = 2; break;
7130           case '-': bScore = 2; break;
7131           case '=': wScore = bScore = 1; break;
7132           case ' ':
7133           case '*': return strdup("busy"); // tourney not finished
7134         }
7135         score[w] += wScore;
7136         score[b] += bScore;
7137         games[w]++;
7138         games[b]++;
7139         nr++;
7140     }
7141     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7142     for(w=0; w<nPlayers; w++) {
7143         bScore = -1;
7144         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7145         ranking[w] = b; points[w] = bScore; score[b] = -2;
7146     }
7147     p = malloc(nPlayers*34+1);
7148     for(w=0; w<nPlayers && w<display; w++)
7149         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7150     free(names[0]);
7151     return p;
7152 }
7153
7154 void
7155 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7156 {       // count all piece types
7157         int p, f, r;
7158         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7159         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7160         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7161                 p = board[r][f];
7162                 pCnt[p]++;
7163                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7164                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7165                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7166                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7167                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7168                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7169         }
7170 }
7171
7172 int
7173 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7174 {
7175         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7176         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7177
7178         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7179         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7180         if(myPawns == 2 && nMine == 3) // KPP
7181             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7182         if(myPawns == 1 && nMine == 2) // KP
7183             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7184         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7185             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7186         if(myPawns) return FALSE;
7187         if(pCnt[WhiteRook+side])
7188             return pCnt[BlackRook-side] ||
7189                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7190                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7191                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7192         if(pCnt[WhiteCannon+side]) {
7193             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7194             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7195         }
7196         if(pCnt[WhiteKnight+side])
7197             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7198         return FALSE;
7199 }
7200
7201 int
7202 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7203 {
7204         VariantClass v = gameInfo.variant;
7205
7206         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7207         if(v == VariantShatranj) return TRUE; // always winnable through baring
7208         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7209         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7210
7211         if(v == VariantXiangqi) {
7212                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7213
7214                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7215                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7216                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7217                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7218                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7219                 if(stale) // we have at least one last-rank P plus perhaps C
7220                     return majors // KPKX
7221                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7222                 else // KCA*E*
7223                     return pCnt[WhiteFerz+side] // KCAK
7224                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7225                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7226                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7227
7228         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7229                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7230
7231                 if(nMine == 1) return FALSE; // bare King
7232                 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
7233                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7234                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7235                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7236                 if(pCnt[WhiteKnight+side])
7237                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7238                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7239                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7240                 if(nBishops)
7241                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7242                 if(pCnt[WhiteAlfil+side])
7243                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7244                 if(pCnt[WhiteWazir+side])
7245                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7246         }
7247
7248         return TRUE;
7249 }
7250
7251 int
7252 Adjudicate(ChessProgramState *cps)
7253 {       // [HGM] some adjudications useful with buggy engines
7254         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7255         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7256         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7257         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7258         int k, count = 0; static int bare = 1;
7259         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7260         Boolean canAdjudicate = !appData.icsActive;
7261
7262         // most tests only when we understand the game, i.e. legality-checking on
7263             if( appData.testLegality )
7264             {   /* [HGM] Some more adjudications for obstinate engines */
7265                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7266                 static int moveCount = 6;
7267                 ChessMove result;
7268                 char *reason = NULL;
7269
7270                 /* Count what is on board. */
7271                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7272
7273                 /* Some material-based adjudications that have to be made before stalemate test */
7274                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7275                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7276                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7277                      if(canAdjudicate && appData.checkMates) {
7278                          if(engineOpponent)
7279                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7280                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7281                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7282                          return 1;
7283                      }
7284                 }
7285
7286                 /* Bare King in Shatranj (loses) or Losers (wins) */
7287                 if( nrW == 1 || nrB == 1) {
7288                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7289                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7290                      if(canAdjudicate && appData.checkMates) {
7291                          if(engineOpponent)
7292                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7293                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7294                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7295                          return 1;
7296                      }
7297                   } else
7298                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7299                   {    /* bare King */
7300                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7301                         if(canAdjudicate && appData.checkMates) {
7302                             /* but only adjudicate if adjudication enabled */
7303                             if(engineOpponent)
7304                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7305                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7306                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7307                             return 1;
7308                         }
7309                   }
7310                 } else bare = 1;
7311
7312
7313             // don't wait for engine to announce game end if we can judge ourselves
7314             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7315               case MT_CHECK:
7316                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7317                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7318                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7319                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7320                             checkCnt++;
7321                         if(checkCnt >= 2) {
7322                             reason = "Xboard adjudication: 3rd check";
7323                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7324                             break;
7325                         }
7326                     }
7327                 }
7328               case MT_NONE:
7329               default:
7330                 break;
7331               case MT_STALEMATE:
7332               case MT_STAINMATE:
7333                 reason = "Xboard adjudication: Stalemate";
7334                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7335                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7336                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7337                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7338                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7339                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7340                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7341                                                                         EP_CHECKMATE : EP_WINS);
7342                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7343                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7344                 }
7345                 break;
7346               case MT_CHECKMATE:
7347                 reason = "Xboard adjudication: Checkmate";
7348                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7349                 break;
7350             }
7351
7352                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7353                     case EP_STALEMATE:
7354                         result = GameIsDrawn; break;
7355                     case EP_CHECKMATE:
7356                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7357                     case EP_WINS:
7358                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7359                     default:
7360                         result = EndOfFile;
7361                 }
7362                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7363                     if(engineOpponent)
7364                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7365                     GameEnds( result, reason, GE_XBOARD );
7366                     return 1;
7367                 }
7368
7369                 /* Next absolutely insufficient mating material. */
7370                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7371                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7372                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7373
7374                      /* always flag draws, for judging claims */
7375                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7376
7377                      if(canAdjudicate && appData.materialDraws) {
7378                          /* but only adjudicate them if adjudication enabled */
7379                          if(engineOpponent) {
7380                            SendToProgram("force\n", engineOpponent); // suppress reply
7381                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7382                          }
7383                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7384                          return 1;
7385                      }
7386                 }
7387
7388                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7389                 if(gameInfo.variant == VariantXiangqi ?
7390                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7391                  : nrW + nrB == 4 &&
7392                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7393                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7394                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7395                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7396                    ) ) {
7397                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7398                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7399                           if(engineOpponent) {
7400                             SendToProgram("force\n", engineOpponent); // suppress reply
7401                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7402                           }
7403                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7404                           return 1;
7405                      }
7406                 } else moveCount = 6;
7407             }
7408         if (appData.debugMode) { int i;
7409             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7410                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7411                     appData.drawRepeats);
7412             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7413               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7414
7415         }
7416
7417         // Repetition draws and 50-move rule can be applied independently of legality testing
7418
7419                 /* Check for rep-draws */
7420                 count = 0;
7421                 for(k = forwardMostMove-2;
7422                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7423                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7424                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7425                     k-=2)
7426                 {   int rights=0;
7427                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7428                         /* compare castling rights */
7429                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7430                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7431                                 rights++; /* King lost rights, while rook still had them */
7432                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7433                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7434                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7435                                    rights++; /* but at least one rook lost them */
7436                         }
7437                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7438                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7439                                 rights++;
7440                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7441                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7442                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7443                                    rights++;
7444                         }
7445                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7446                             && appData.drawRepeats > 1) {
7447                              /* adjudicate after user-specified nr of repeats */
7448                              int result = GameIsDrawn;
7449                              char *details = "XBoard adjudication: repetition draw";
7450                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7451                                 // [HGM] xiangqi: check for forbidden perpetuals
7452                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7453                                 for(m=forwardMostMove; m>k; m-=2) {
7454                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7455                                         ourPerpetual = 0; // the current mover did not always check
7456                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7457                                         hisPerpetual = 0; // the opponent did not always check
7458                                 }
7459                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7460                                                                         ourPerpetual, hisPerpetual);
7461                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7462                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7463                                     details = "Xboard adjudication: perpetual checking";
7464                                 } else
7465                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7466                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7467                                 } else
7468                                 // Now check for perpetual chases
7469                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7470                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7471                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7472                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7473                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7474                                         details = "Xboard adjudication: perpetual chasing";
7475                                     } else
7476                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7477                                         break; // Abort repetition-checking loop.
7478                                 }
7479                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7480                              }
7481                              if(engineOpponent) {
7482                                SendToProgram("force\n", engineOpponent); // suppress reply
7483                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7484                              }
7485                              GameEnds( result, details, GE_XBOARD );
7486                              return 1;
7487                         }
7488                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7489                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7490                     }
7491                 }
7492
7493                 /* Now we test for 50-move draws. Determine ply count */
7494                 count = forwardMostMove;
7495                 /* look for last irreversble move */
7496                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7497                     count--;
7498                 /* if we hit starting position, add initial plies */
7499                 if( count == backwardMostMove )
7500                     count -= initialRulePlies;
7501                 count = forwardMostMove - count;
7502                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7503                         // adjust reversible move counter for checks in Xiangqi
7504                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7505                         if(i < backwardMostMove) i = backwardMostMove;
7506                         while(i <= forwardMostMove) {
7507                                 lastCheck = inCheck; // check evasion does not count
7508                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7509                                 if(inCheck || lastCheck) count--; // check does not count
7510                                 i++;
7511                         }
7512                 }
7513                 if( count >= 100)
7514                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7515                          /* this is used to judge if draw claims are legal */
7516                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7517                          if(engineOpponent) {
7518                            SendToProgram("force\n", engineOpponent); // suppress reply
7519                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7520                          }
7521                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7522                          return 1;
7523                 }
7524
7525                 /* if draw offer is pending, treat it as a draw claim
7526                  * when draw condition present, to allow engines a way to
7527                  * claim draws before making their move to avoid a race
7528                  * condition occurring after their move
7529                  */
7530                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7531                          char *p = NULL;
7532                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7533                              p = "Draw claim: 50-move rule";
7534                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7535                              p = "Draw claim: 3-fold repetition";
7536                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7537                              p = "Draw claim: insufficient mating material";
7538                          if( p != NULL && canAdjudicate) {
7539                              if(engineOpponent) {
7540                                SendToProgram("force\n", engineOpponent); // suppress reply
7541                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7542                              }
7543                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7544                              return 1;
7545                          }
7546                 }
7547
7548                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7549                     if(engineOpponent) {
7550                       SendToProgram("force\n", engineOpponent); // suppress reply
7551                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7552                     }
7553                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7554                     return 1;
7555                 }
7556         return 0;
7557 }
7558
7559 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7560 {   // [HGM] book: this routine intercepts moves to simulate book replies
7561     char *bookHit = NULL;
7562
7563     //first determine if the incoming move brings opponent into his book
7564     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7565         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7566     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7567     if(bookHit != NULL && !cps->bookSuspend) {
7568         // make sure opponent is not going to reply after receiving move to book position
7569         SendToProgram("force\n", cps);
7570         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7571     }
7572     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7573     // now arrange restart after book miss
7574     if(bookHit) {
7575         // after a book hit we never send 'go', and the code after the call to this routine
7576         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7577         char buf[MSG_SIZ];
7578         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7579         SendToProgram(buf, cps);
7580         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7581     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7582         SendToProgram("go\n", cps);
7583         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7584     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7585         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7586             SendToProgram("go\n", cps);
7587         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7588     }
7589     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7590 }
7591
7592 char *savedMessage;
7593 ChessProgramState *savedState;
7594 void DeferredBookMove(void)
7595 {
7596         if(savedState->lastPing != savedState->lastPong)
7597                     ScheduleDelayedEvent(DeferredBookMove, 10);
7598         else
7599         HandleMachineMove(savedMessage, savedState);
7600 }
7601
7602 void
7603 HandleMachineMove(message, cps)
7604      char *message;
7605      ChessProgramState *cps;
7606 {
7607     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7608     char realname[MSG_SIZ];
7609     int fromX, fromY, toX, toY;
7610     ChessMove moveType;
7611     char promoChar;
7612     char *p;
7613     int machineWhite;
7614     char *bookHit;
7615
7616     cps->userError = 0;
7617
7618 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7619     /*
7620      * Kludge to ignore BEL characters
7621      */
7622     while (*message == '\007') message++;
7623
7624     /*
7625      * [HGM] engine debug message: ignore lines starting with '#' character
7626      */
7627     if(cps->debug && *message == '#') return;
7628
7629     /*
7630      * Look for book output
7631      */
7632     if (cps == &first && bookRequested) {
7633         if (message[0] == '\t' || message[0] == ' ') {
7634             /* Part of the book output is here; append it */
7635             strcat(bookOutput, message);
7636             strcat(bookOutput, "  \n");
7637             return;
7638         } else if (bookOutput[0] != NULLCHAR) {
7639             /* All of book output has arrived; display it */
7640             char *p = bookOutput;
7641             while (*p != NULLCHAR) {
7642                 if (*p == '\t') *p = ' ';
7643                 p++;
7644             }
7645             DisplayInformation(bookOutput);
7646             bookRequested = FALSE;
7647             /* Fall through to parse the current output */
7648         }
7649     }
7650
7651     /*
7652      * Look for machine move.
7653      */
7654     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7655         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7656     {
7657         /* This method is only useful on engines that support ping */
7658         if (cps->lastPing != cps->lastPong) {
7659           if (gameMode == BeginningOfGame) {
7660             /* Extra move from before last new; ignore */
7661             if (appData.debugMode) {
7662                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7663             }
7664           } else {
7665             if (appData.debugMode) {
7666                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7667                         cps->which, gameMode);
7668             }
7669
7670             SendToProgram("undo\n", cps);
7671           }
7672           return;
7673         }
7674
7675         switch (gameMode) {
7676           case BeginningOfGame:
7677             /* Extra move from before last reset; ignore */
7678             if (appData.debugMode) {
7679                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7680             }
7681             return;
7682
7683           case EndOfGame:
7684           case IcsIdle:
7685           default:
7686             /* Extra move after we tried to stop.  The mode test is
7687                not a reliable way of detecting this problem, but it's
7688                the best we can do on engines that don't support ping.
7689             */
7690             if (appData.debugMode) {
7691                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7692                         cps->which, gameMode);
7693             }
7694             SendToProgram("undo\n", cps);
7695             return;
7696
7697           case MachinePlaysWhite:
7698           case IcsPlayingWhite:
7699             machineWhite = TRUE;
7700             break;
7701
7702           case MachinePlaysBlack:
7703           case IcsPlayingBlack:
7704             machineWhite = FALSE;
7705             break;
7706
7707           case TwoMachinesPlay:
7708             machineWhite = (cps->twoMachinesColor[0] == 'w');
7709             break;
7710         }
7711         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7712             if (appData.debugMode) {
7713                 fprintf(debugFP,
7714                         "Ignoring move out of turn by %s, gameMode %d"
7715                         ", forwardMost %d\n",
7716                         cps->which, gameMode, forwardMostMove);
7717             }
7718             return;
7719         }
7720
7721     if (appData.debugMode) { int f = forwardMostMove;
7722         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7723                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7724                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7725     }
7726         if(cps->alphaRank) AlphaRank(machineMove, 4);
7727         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7728                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7729             /* Machine move could not be parsed; ignore it. */
7730           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7731                     machineMove, _(cps->which));
7732             DisplayError(buf1, 0);
7733             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7734                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7735             if (gameMode == TwoMachinesPlay) {
7736               GameEnds(machineWhite ? BlackWins : WhiteWins,
7737                        buf1, GE_XBOARD);
7738             }
7739             return;
7740         }
7741
7742         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7743         /* So we have to redo legality test with true e.p. status here,  */
7744         /* to make sure an illegal e.p. capture does not slip through,   */
7745         /* to cause a forfeit on a justified illegal-move complaint      */
7746         /* of the opponent.                                              */
7747         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7748            ChessMove moveType;
7749            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7750                              fromY, fromX, toY, toX, promoChar);
7751             if (appData.debugMode) {
7752                 int i;
7753                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7754                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7755                 fprintf(debugFP, "castling rights\n");
7756             }
7757             if(moveType == IllegalMove) {
7758               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7759                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7760                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7761                            buf1, GE_XBOARD);
7762                 return;
7763            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7764            /* [HGM] Kludge to handle engines that send FRC-style castling
7765               when they shouldn't (like TSCP-Gothic) */
7766            switch(moveType) {
7767              case WhiteASideCastleFR:
7768              case BlackASideCastleFR:
7769                toX+=2;
7770                currentMoveString[2]++;
7771                break;
7772              case WhiteHSideCastleFR:
7773              case BlackHSideCastleFR:
7774                toX--;
7775                currentMoveString[2]--;
7776                break;
7777              default: ; // nothing to do, but suppresses warning of pedantic compilers
7778            }
7779         }
7780         hintRequested = FALSE;
7781         lastHint[0] = NULLCHAR;
7782         bookRequested = FALSE;
7783         /* Program may be pondering now */
7784         cps->maybeThinking = TRUE;
7785         if (cps->sendTime == 2) cps->sendTime = 1;
7786         if (cps->offeredDraw) cps->offeredDraw--;
7787
7788         /* [AS] Save move info*/
7789         pvInfoList[ forwardMostMove ].score = programStats.score;
7790         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7791         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7792
7793         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7794
7795         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7796         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7797             int count = 0;
7798
7799             while( count < adjudicateLossPlies ) {
7800                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7801
7802                 if( count & 1 ) {
7803                     score = -score; /* Flip score for winning side */
7804                 }
7805
7806                 if( score > adjudicateLossThreshold ) {
7807                     break;
7808                 }
7809
7810                 count++;
7811             }
7812
7813             if( count >= adjudicateLossPlies ) {
7814                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7815
7816                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7817                     "Xboard adjudication",
7818                     GE_XBOARD );
7819
7820                 return;
7821             }
7822         }
7823
7824         if(Adjudicate(cps)) {
7825             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7826             return; // [HGM] adjudicate: for all automatic game ends
7827         }
7828
7829 #if ZIPPY
7830         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7831             first.initDone) {
7832           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7833                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7834                 SendToICS("draw ");
7835                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7836           }
7837           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7838           ics_user_moved = 1;
7839           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7840                 char buf[3*MSG_SIZ];
7841
7842                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7843                         programStats.score / 100.,
7844                         programStats.depth,
7845                         programStats.time / 100.,
7846                         (unsigned int)programStats.nodes,
7847                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7848                         programStats.movelist);
7849                 SendToICS(buf);
7850 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7851           }
7852         }
7853 #endif
7854
7855         /* [AS] Clear stats for next move */
7856         ClearProgramStats();
7857         thinkOutput[0] = NULLCHAR;
7858         hiddenThinkOutputState = 0;
7859
7860         bookHit = NULL;
7861         if (gameMode == TwoMachinesPlay) {
7862             /* [HGM] relaying draw offers moved to after reception of move */
7863             /* and interpreting offer as claim if it brings draw condition */
7864             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7865                 SendToProgram("draw\n", cps->other);
7866             }
7867             if (cps->other->sendTime) {
7868                 SendTimeRemaining(cps->other,
7869                                   cps->other->twoMachinesColor[0] == 'w');
7870             }
7871             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7872             if (firstMove && !bookHit) {
7873                 firstMove = FALSE;
7874                 if (cps->other->useColors) {
7875                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7876                 }
7877                 SendToProgram("go\n", cps->other);
7878             }
7879             cps->other->maybeThinking = TRUE;
7880         }
7881
7882         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7883
7884         if (!pausing && appData.ringBellAfterMoves) {
7885             RingBell();
7886         }
7887
7888         /*
7889          * Reenable menu items that were disabled while
7890          * machine was thinking
7891          */
7892         if (gameMode != TwoMachinesPlay)
7893             SetUserThinkingEnables();
7894
7895         // [HGM] book: after book hit opponent has received move and is now in force mode
7896         // force the book reply into it, and then fake that it outputted this move by jumping
7897         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7898         if(bookHit) {
7899                 static char bookMove[MSG_SIZ]; // a bit generous?
7900
7901                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7902                 strcat(bookMove, bookHit);
7903                 message = bookMove;
7904                 cps = cps->other;
7905                 programStats.nodes = programStats.depth = programStats.time =
7906                 programStats.score = programStats.got_only_move = 0;
7907                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7908
7909                 if(cps->lastPing != cps->lastPong) {
7910                     savedMessage = message; // args for deferred call
7911                     savedState = cps;
7912                     ScheduleDelayedEvent(DeferredBookMove, 10);
7913                     return;
7914                 }
7915                 goto FakeBookMove;
7916         }
7917
7918         return;
7919     }
7920
7921     /* Set special modes for chess engines.  Later something general
7922      *  could be added here; for now there is just one kludge feature,
7923      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7924      *  when "xboard" is given as an interactive command.
7925      */
7926     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7927         cps->useSigint = FALSE;
7928         cps->useSigterm = FALSE;
7929     }
7930     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7931       ParseFeatures(message+8, cps);
7932       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7933     }
7934
7935     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7936       int dummy, s=6; char buf[MSG_SIZ];
7937       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7938       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7939       ParseFEN(boards[0], &dummy, message+s);
7940       DrawPosition(TRUE, boards[0]);
7941       startedFromSetupPosition = TRUE;
7942       return;
7943     }
7944     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7945      * want this, I was asked to put it in, and obliged.
7946      */
7947     if (!strncmp(message, "setboard ", 9)) {
7948         Board initial_position;
7949
7950         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7951
7952         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7953             DisplayError(_("Bad FEN received from engine"), 0);
7954             return ;
7955         } else {
7956            Reset(TRUE, FALSE);
7957            CopyBoard(boards[0], initial_position);
7958            initialRulePlies = FENrulePlies;
7959            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7960            else gameMode = MachinePlaysBlack;
7961            DrawPosition(FALSE, boards[currentMove]);
7962         }
7963         return;
7964     }
7965
7966     /*
7967      * Look for communication commands
7968      */
7969     if (!strncmp(message, "telluser ", 9)) {
7970         if(message[9] == '\\' && message[10] == '\\')
7971             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7972         DisplayNote(message + 9);
7973         return;
7974     }
7975     if (!strncmp(message, "tellusererror ", 14)) {
7976         cps->userError = 1;
7977         if(message[14] == '\\' && message[15] == '\\')
7978             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7979         DisplayError(message + 14, 0);
7980         return;
7981     }
7982     if (!strncmp(message, "tellopponent ", 13)) {
7983       if (appData.icsActive) {
7984         if (loggedOn) {
7985           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7986           SendToICS(buf1);
7987         }
7988       } else {
7989         DisplayNote(message + 13);
7990       }
7991       return;
7992     }
7993     if (!strncmp(message, "tellothers ", 11)) {
7994       if (appData.icsActive) {
7995         if (loggedOn) {
7996           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7997           SendToICS(buf1);
7998         }
7999       }
8000       return;
8001     }
8002     if (!strncmp(message, "tellall ", 8)) {
8003       if (appData.icsActive) {
8004         if (loggedOn) {
8005           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8006           SendToICS(buf1);
8007         }
8008       } else {
8009         DisplayNote(message + 8);
8010       }
8011       return;
8012     }
8013     if (strncmp(message, "warning", 7) == 0) {
8014         /* Undocumented feature, use tellusererror in new code */
8015         DisplayError(message, 0);
8016         return;
8017     }
8018     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8019         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8020         strcat(realname, " query");
8021         AskQuestion(realname, buf2, buf1, cps->pr);
8022         return;
8023     }
8024     /* Commands from the engine directly to ICS.  We don't allow these to be
8025      *  sent until we are logged on. Crafty kibitzes have been known to
8026      *  interfere with the login process.
8027      */
8028     if (loggedOn) {
8029         if (!strncmp(message, "tellics ", 8)) {
8030             SendToICS(message + 8);
8031             SendToICS("\n");
8032             return;
8033         }
8034         if (!strncmp(message, "tellicsnoalias ", 15)) {
8035             SendToICS(ics_prefix);
8036             SendToICS(message + 15);
8037             SendToICS("\n");
8038             return;
8039         }
8040         /* The following are for backward compatibility only */
8041         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8042             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8043             SendToICS(ics_prefix);
8044             SendToICS(message);
8045             SendToICS("\n");
8046             return;
8047         }
8048     }
8049     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8050         return;
8051     }
8052     /*
8053      * If the move is illegal, cancel it and redraw the board.
8054      * Also deal with other error cases.  Matching is rather loose
8055      * here to accommodate engines written before the spec.
8056      */
8057     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8058         strncmp(message, "Error", 5) == 0) {
8059         if (StrStr(message, "name") ||
8060             StrStr(message, "rating") || StrStr(message, "?") ||
8061             StrStr(message, "result") || StrStr(message, "board") ||
8062             StrStr(message, "bk") || StrStr(message, "computer") ||
8063             StrStr(message, "variant") || StrStr(message, "hint") ||
8064             StrStr(message, "random") || StrStr(message, "depth") ||
8065             StrStr(message, "accepted")) {
8066             return;
8067         }
8068         if (StrStr(message, "protover")) {
8069           /* Program is responding to input, so it's apparently done
8070              initializing, and this error message indicates it is
8071              protocol version 1.  So we don't need to wait any longer
8072              for it to initialize and send feature commands. */
8073           FeatureDone(cps, 1);
8074           cps->protocolVersion = 1;
8075           return;
8076         }
8077         cps->maybeThinking = FALSE;
8078
8079         if (StrStr(message, "draw")) {
8080             /* Program doesn't have "draw" command */
8081             cps->sendDrawOffers = 0;
8082             return;
8083         }
8084         if (cps->sendTime != 1 &&
8085             (StrStr(message, "time") || StrStr(message, "otim"))) {
8086           /* Program apparently doesn't have "time" or "otim" command */
8087           cps->sendTime = 0;
8088           return;
8089         }
8090         if (StrStr(message, "analyze")) {
8091             cps->analysisSupport = FALSE;
8092             cps->analyzing = FALSE;
8093             Reset(FALSE, TRUE);
8094             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8095             DisplayError(buf2, 0);
8096             return;
8097         }
8098         if (StrStr(message, "(no matching move)st")) {
8099           /* Special kludge for GNU Chess 4 only */
8100           cps->stKludge = TRUE;
8101           SendTimeControl(cps, movesPerSession, timeControl,
8102                           timeIncrement, appData.searchDepth,
8103                           searchTime);
8104           return;
8105         }
8106         if (StrStr(message, "(no matching move)sd")) {
8107           /* Special kludge for GNU Chess 4 only */
8108           cps->sdKludge = TRUE;
8109           SendTimeControl(cps, movesPerSession, timeControl,
8110                           timeIncrement, appData.searchDepth,
8111                           searchTime);
8112           return;
8113         }
8114         if (!StrStr(message, "llegal")) {
8115             return;
8116         }
8117         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8118             gameMode == IcsIdle) return;
8119         if (forwardMostMove <= backwardMostMove) return;
8120         if (pausing) PauseEvent();
8121       if(appData.forceIllegal) {
8122             // [HGM] illegal: machine refused move; force position after move into it
8123           SendToProgram("force\n", cps);
8124           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8125                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8126                 // when black is to move, while there might be nothing on a2 or black
8127                 // might already have the move. So send the board as if white has the move.
8128                 // But first we must change the stm of the engine, as it refused the last move
8129                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8130                 if(WhiteOnMove(forwardMostMove)) {
8131                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8132                     SendBoard(cps, forwardMostMove); // kludgeless board
8133                 } else {
8134                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8135                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8136                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8137                 }
8138           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8139             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8140                  gameMode == TwoMachinesPlay)
8141               SendToProgram("go\n", cps);
8142             return;
8143       } else
8144         if (gameMode == PlayFromGameFile) {
8145             /* Stop reading this game file */
8146             gameMode = EditGame;
8147             ModeHighlight();
8148         }
8149         /* [HGM] illegal-move claim should forfeit game when Xboard */
8150         /* only passes fully legal moves                            */
8151         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8152             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8153                                 "False illegal-move claim", GE_XBOARD );
8154             return; // do not take back move we tested as valid
8155         }
8156         currentMove = forwardMostMove-1;
8157         DisplayMove(currentMove-1); /* before DisplayMoveError */
8158         SwitchClocks(forwardMostMove-1); // [HGM] race
8159         DisplayBothClocks();
8160         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8161                 parseList[currentMove], _(cps->which));
8162         DisplayMoveError(buf1);
8163         DrawPosition(FALSE, boards[currentMove]);
8164         return;
8165     }
8166     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8167         /* Program has a broken "time" command that
8168            outputs a string not ending in newline.
8169            Don't use it. */
8170         cps->sendTime = 0;
8171     }
8172
8173     /*
8174      * If chess program startup fails, exit with an error message.
8175      * Attempts to recover here are futile.
8176      */
8177     if ((StrStr(message, "unknown host") != NULL)
8178         || (StrStr(message, "No remote directory") != NULL)
8179         || (StrStr(message, "not found") != NULL)
8180         || (StrStr(message, "No such file") != NULL)
8181         || (StrStr(message, "can't alloc") != NULL)
8182         || (StrStr(message, "Permission denied") != NULL)) {
8183
8184         cps->maybeThinking = FALSE;
8185         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8186                 _(cps->which), cps->program, cps->host, message);
8187         RemoveInputSource(cps->isr);
8188         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8189             if(cps == &first) appData.noChessProgram = TRUE;
8190             DisplayError(buf1, 0);
8191         }
8192         return;
8193     }
8194
8195     /*
8196      * Look for hint output
8197      */
8198     if (sscanf(message, "Hint: %s", buf1) == 1) {
8199         if (cps == &first && hintRequested) {
8200             hintRequested = FALSE;
8201             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8202                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8203                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8204                                     PosFlags(forwardMostMove),
8205                                     fromY, fromX, toY, toX, promoChar, buf1);
8206                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8207                 DisplayInformation(buf2);
8208             } else {
8209                 /* Hint move could not be parsed!? */
8210               snprintf(buf2, sizeof(buf2),
8211                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8212                         buf1, _(cps->which));
8213                 DisplayError(buf2, 0);
8214             }
8215         } else {
8216           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8217         }
8218         return;
8219     }
8220
8221     /*
8222      * Ignore other messages if game is not in progress
8223      */
8224     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8225         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8226
8227     /*
8228      * look for win, lose, draw, or draw offer
8229      */
8230     if (strncmp(message, "1-0", 3) == 0) {
8231         char *p, *q, *r = "";
8232         p = strchr(message, '{');
8233         if (p) {
8234             q = strchr(p, '}');
8235             if (q) {
8236                 *q = NULLCHAR;
8237                 r = p + 1;
8238             }
8239         }
8240         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8241         return;
8242     } else if (strncmp(message, "0-1", 3) == 0) {
8243         char *p, *q, *r = "";
8244         p = strchr(message, '{');
8245         if (p) {
8246             q = strchr(p, '}');
8247             if (q) {
8248                 *q = NULLCHAR;
8249                 r = p + 1;
8250             }
8251         }
8252         /* Kludge for Arasan 4.1 bug */
8253         if (strcmp(r, "Black resigns") == 0) {
8254             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8255             return;
8256         }
8257         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8258         return;
8259     } else if (strncmp(message, "1/2", 3) == 0) {
8260         char *p, *q, *r = "";
8261         p = strchr(message, '{');
8262         if (p) {
8263             q = strchr(p, '}');
8264             if (q) {
8265                 *q = NULLCHAR;
8266                 r = p + 1;
8267             }
8268         }
8269
8270         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8271         return;
8272
8273     } else if (strncmp(message, "White resign", 12) == 0) {
8274         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8275         return;
8276     } else if (strncmp(message, "Black resign", 12) == 0) {
8277         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8278         return;
8279     } else if (strncmp(message, "White matches", 13) == 0 ||
8280                strncmp(message, "Black matches", 13) == 0   ) {
8281         /* [HGM] ignore GNUShogi noises */
8282         return;
8283     } else if (strncmp(message, "White", 5) == 0 &&
8284                message[5] != '(' &&
8285                StrStr(message, "Black") == NULL) {
8286         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8287         return;
8288     } else if (strncmp(message, "Black", 5) == 0 &&
8289                message[5] != '(') {
8290         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8291         return;
8292     } else if (strcmp(message, "resign") == 0 ||
8293                strcmp(message, "computer resigns") == 0) {
8294         switch (gameMode) {
8295           case MachinePlaysBlack:
8296           case IcsPlayingBlack:
8297             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8298             break;
8299           case MachinePlaysWhite:
8300           case IcsPlayingWhite:
8301             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8302             break;
8303           case TwoMachinesPlay:
8304             if (cps->twoMachinesColor[0] == 'w')
8305               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8306             else
8307               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8308             break;
8309           default:
8310             /* can't happen */
8311             break;
8312         }
8313         return;
8314     } else if (strncmp(message, "opponent mates", 14) == 0) {
8315         switch (gameMode) {
8316           case MachinePlaysBlack:
8317           case IcsPlayingBlack:
8318             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8319             break;
8320           case MachinePlaysWhite:
8321           case IcsPlayingWhite:
8322             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8323             break;
8324           case TwoMachinesPlay:
8325             if (cps->twoMachinesColor[0] == 'w')
8326               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8327             else
8328               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8329             break;
8330           default:
8331             /* can't happen */
8332             break;
8333         }
8334         return;
8335     } else if (strncmp(message, "computer mates", 14) == 0) {
8336         switch (gameMode) {
8337           case MachinePlaysBlack:
8338           case IcsPlayingBlack:
8339             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8340             break;
8341           case MachinePlaysWhite:
8342           case IcsPlayingWhite:
8343             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8344             break;
8345           case TwoMachinesPlay:
8346             if (cps->twoMachinesColor[0] == 'w')
8347               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8348             else
8349               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8350             break;
8351           default:
8352             /* can't happen */
8353             break;
8354         }
8355         return;
8356     } else if (strncmp(message, "checkmate", 9) == 0) {
8357         if (WhiteOnMove(forwardMostMove)) {
8358             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8359         } else {
8360             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8361         }
8362         return;
8363     } else if (strstr(message, "Draw") != NULL ||
8364                strstr(message, "game is a draw") != NULL) {
8365         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8366         return;
8367     } else if (strstr(message, "offer") != NULL &&
8368                strstr(message, "draw") != NULL) {
8369 #if ZIPPY
8370         if (appData.zippyPlay && first.initDone) {
8371             /* Relay offer to ICS */
8372             SendToICS(ics_prefix);
8373             SendToICS("draw\n");
8374         }
8375 #endif
8376         cps->offeredDraw = 2; /* valid until this engine moves twice */
8377         if (gameMode == TwoMachinesPlay) {
8378             if (cps->other->offeredDraw) {
8379                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8380             /* [HGM] in two-machine mode we delay relaying draw offer      */
8381             /* until after we also have move, to see if it is really claim */
8382             }
8383         } else if (gameMode == MachinePlaysWhite ||
8384                    gameMode == MachinePlaysBlack) {
8385           if (userOfferedDraw) {
8386             DisplayInformation(_("Machine accepts your draw offer"));
8387             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8388           } else {
8389             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8390           }
8391         }
8392     }
8393
8394
8395     /*
8396      * Look for thinking output
8397      */
8398     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8399           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8400                                 ) {
8401         int plylev, mvleft, mvtot, curscore, time;
8402         char mvname[MOVE_LEN];
8403         u64 nodes; // [DM]
8404         char plyext;
8405         int ignore = FALSE;
8406         int prefixHint = FALSE;
8407         mvname[0] = NULLCHAR;
8408
8409         switch (gameMode) {
8410           case MachinePlaysBlack:
8411           case IcsPlayingBlack:
8412             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8413             break;
8414           case MachinePlaysWhite:
8415           case IcsPlayingWhite:
8416             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8417             break;
8418           case AnalyzeMode:
8419           case AnalyzeFile:
8420             break;
8421           case IcsObserving: /* [DM] icsEngineAnalyze */
8422             if (!appData.icsEngineAnalyze) ignore = TRUE;
8423             break;
8424           case TwoMachinesPlay:
8425             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8426                 ignore = TRUE;
8427             }
8428             break;
8429           default:
8430             ignore = TRUE;
8431             break;
8432         }
8433
8434         if (!ignore) {
8435             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8436             buf1[0] = NULLCHAR;
8437             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8438                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8439
8440                 if (plyext != ' ' && plyext != '\t') {
8441                     time *= 100;
8442                 }
8443
8444                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8445                 if( cps->scoreIsAbsolute &&
8446                     ( gameMode == MachinePlaysBlack ||
8447                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8448                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8449                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8450                      !WhiteOnMove(currentMove)
8451                     ) )
8452                 {
8453                     curscore = -curscore;
8454                 }
8455
8456
8457                 tempStats.depth = plylev;
8458                 tempStats.nodes = nodes;
8459                 tempStats.time = time;
8460                 tempStats.score = curscore;
8461                 tempStats.got_only_move = 0;
8462
8463                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8464                         int ticklen;
8465
8466                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8467                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8468                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8469                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8470                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8471                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8472                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8473                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8474                 }
8475
8476                 /* Buffer overflow protection */
8477                 if (buf1[0] != NULLCHAR) {
8478                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8479                         && appData.debugMode) {
8480                         fprintf(debugFP,
8481                                 "PV is too long; using the first %u bytes.\n",
8482                                 (unsigned) sizeof(tempStats.movelist) - 1);
8483                     }
8484
8485                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8486                 } else {
8487                     sprintf(tempStats.movelist, " no PV\n");
8488                 }
8489
8490                 if (tempStats.seen_stat) {
8491                     tempStats.ok_to_send = 1;
8492                 }
8493
8494                 if (strchr(tempStats.movelist, '(') != NULL) {
8495                     tempStats.line_is_book = 1;
8496                     tempStats.nr_moves = 0;
8497                     tempStats.moves_left = 0;
8498                 } else {
8499                     tempStats.line_is_book = 0;
8500                 }
8501
8502                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8503                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8504
8505                 SendProgramStatsToFrontend( cps, &tempStats );
8506
8507                 /*
8508                     [AS] Protect the thinkOutput buffer from overflow... this
8509                     is only useful if buf1 hasn't overflowed first!
8510                 */
8511                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8512                          plylev,
8513                          (gameMode == TwoMachinesPlay ?
8514                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8515                          ((double) curscore) / 100.0,
8516                          prefixHint ? lastHint : "",
8517                          prefixHint ? " " : "" );
8518
8519                 if( buf1[0] != NULLCHAR ) {
8520                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8521
8522                     if( strlen(buf1) > max_len ) {
8523                         if( appData.debugMode) {
8524                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8525                         }
8526                         buf1[max_len+1] = '\0';
8527                     }
8528
8529                     strcat( thinkOutput, buf1 );
8530                 }
8531
8532                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8533                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8534                     DisplayMove(currentMove - 1);
8535                 }
8536                 return;
8537
8538             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8539                 /* crafty (9.25+) says "(only move) <move>"
8540                  * if there is only 1 legal move
8541                  */
8542                 sscanf(p, "(only move) %s", buf1);
8543                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8544                 sprintf(programStats.movelist, "%s (only move)", buf1);
8545                 programStats.depth = 1;
8546                 programStats.nr_moves = 1;
8547                 programStats.moves_left = 1;
8548                 programStats.nodes = 1;
8549                 programStats.time = 1;
8550                 programStats.got_only_move = 1;
8551
8552                 /* Not really, but we also use this member to
8553                    mean "line isn't going to change" (Crafty
8554                    isn't searching, so stats won't change) */
8555                 programStats.line_is_book = 1;
8556
8557                 SendProgramStatsToFrontend( cps, &programStats );
8558
8559                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8560                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8561                     DisplayMove(currentMove - 1);
8562                 }
8563                 return;
8564             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8565                               &time, &nodes, &plylev, &mvleft,
8566                               &mvtot, mvname) >= 5) {
8567                 /* The stat01: line is from Crafty (9.29+) in response
8568                    to the "." command */
8569                 programStats.seen_stat = 1;
8570                 cps->maybeThinking = TRUE;
8571
8572                 if (programStats.got_only_move || !appData.periodicUpdates)
8573                   return;
8574
8575                 programStats.depth = plylev;
8576                 programStats.time = time;
8577                 programStats.nodes = nodes;
8578                 programStats.moves_left = mvleft;
8579                 programStats.nr_moves = mvtot;
8580                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8581                 programStats.ok_to_send = 1;
8582                 programStats.movelist[0] = '\0';
8583
8584                 SendProgramStatsToFrontend( cps, &programStats );
8585
8586                 return;
8587
8588             } else if (strncmp(message,"++",2) == 0) {
8589                 /* Crafty 9.29+ outputs this */
8590                 programStats.got_fail = 2;
8591                 return;
8592
8593             } else if (strncmp(message,"--",2) == 0) {
8594                 /* Crafty 9.29+ outputs this */
8595                 programStats.got_fail = 1;
8596                 return;
8597
8598             } else if (thinkOutput[0] != NULLCHAR &&
8599                        strncmp(message, "    ", 4) == 0) {
8600                 unsigned message_len;
8601
8602                 p = message;
8603                 while (*p && *p == ' ') p++;
8604
8605                 message_len = strlen( p );
8606
8607                 /* [AS] Avoid buffer overflow */
8608                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8609                     strcat(thinkOutput, " ");
8610                     strcat(thinkOutput, p);
8611                 }
8612
8613                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8614                     strcat(programStats.movelist, " ");
8615                     strcat(programStats.movelist, p);
8616                 }
8617
8618                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8619                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8620                     DisplayMove(currentMove - 1);
8621                 }
8622                 return;
8623             }
8624         }
8625         else {
8626             buf1[0] = NULLCHAR;
8627
8628             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8629                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8630             {
8631                 ChessProgramStats cpstats;
8632
8633                 if (plyext != ' ' && plyext != '\t') {
8634                     time *= 100;
8635                 }
8636
8637                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8638                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8639                     curscore = -curscore;
8640                 }
8641
8642                 cpstats.depth = plylev;
8643                 cpstats.nodes = nodes;
8644                 cpstats.time = time;
8645                 cpstats.score = curscore;
8646                 cpstats.got_only_move = 0;
8647                 cpstats.movelist[0] = '\0';
8648
8649                 if (buf1[0] != NULLCHAR) {
8650                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8651                 }
8652
8653                 cpstats.ok_to_send = 0;
8654                 cpstats.line_is_book = 0;
8655                 cpstats.nr_moves = 0;
8656                 cpstats.moves_left = 0;
8657
8658                 SendProgramStatsToFrontend( cps, &cpstats );
8659             }
8660         }
8661     }
8662 }
8663
8664
8665 /* Parse a game score from the character string "game", and
8666    record it as the history of the current game.  The game
8667    score is NOT assumed to start from the standard position.
8668    The display is not updated in any way.
8669    */
8670 void
8671 ParseGameHistory(game)
8672      char *game;
8673 {
8674     ChessMove moveType;
8675     int fromX, fromY, toX, toY, boardIndex;
8676     char promoChar;
8677     char *p, *q;
8678     char buf[MSG_SIZ];
8679
8680     if (appData.debugMode)
8681       fprintf(debugFP, "Parsing game history: %s\n", game);
8682
8683     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8684     gameInfo.site = StrSave(appData.icsHost);
8685     gameInfo.date = PGNDate();
8686     gameInfo.round = StrSave("-");
8687
8688     /* Parse out names of players */
8689     while (*game == ' ') game++;
8690     p = buf;
8691     while (*game != ' ') *p++ = *game++;
8692     *p = NULLCHAR;
8693     gameInfo.white = StrSave(buf);
8694     while (*game == ' ') game++;
8695     p = buf;
8696     while (*game != ' ' && *game != '\n') *p++ = *game++;
8697     *p = NULLCHAR;
8698     gameInfo.black = StrSave(buf);
8699
8700     /* Parse moves */
8701     boardIndex = blackPlaysFirst ? 1 : 0;
8702     yynewstr(game);
8703     for (;;) {
8704         yyboardindex = boardIndex;
8705         moveType = (ChessMove) Myylex();
8706         switch (moveType) {
8707           case IllegalMove:             /* maybe suicide chess, etc. */
8708   if (appData.debugMode) {
8709     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8710     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8711     setbuf(debugFP, NULL);
8712   }
8713           case WhitePromotion:
8714           case BlackPromotion:
8715           case WhiteNonPromotion:
8716           case BlackNonPromotion:
8717           case NormalMove:
8718           case WhiteCapturesEnPassant:
8719           case BlackCapturesEnPassant:
8720           case WhiteKingSideCastle:
8721           case WhiteQueenSideCastle:
8722           case BlackKingSideCastle:
8723           case BlackQueenSideCastle:
8724           case WhiteKingSideCastleWild:
8725           case WhiteQueenSideCastleWild:
8726           case BlackKingSideCastleWild:
8727           case BlackQueenSideCastleWild:
8728           /* PUSH Fabien */
8729           case WhiteHSideCastleFR:
8730           case WhiteASideCastleFR:
8731           case BlackHSideCastleFR:
8732           case BlackASideCastleFR:
8733           /* POP Fabien */
8734             fromX = currentMoveString[0] - AAA;
8735             fromY = currentMoveString[1] - ONE;
8736             toX = currentMoveString[2] - AAA;
8737             toY = currentMoveString[3] - ONE;
8738             promoChar = currentMoveString[4];
8739             break;
8740           case WhiteDrop:
8741           case BlackDrop:
8742             fromX = moveType == WhiteDrop ?
8743               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8744             (int) CharToPiece(ToLower(currentMoveString[0]));
8745             fromY = DROP_RANK;
8746             toX = currentMoveString[2] - AAA;
8747             toY = currentMoveString[3] - ONE;
8748             promoChar = NULLCHAR;
8749             break;
8750           case AmbiguousMove:
8751             /* bug? */
8752             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8753   if (appData.debugMode) {
8754     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8755     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8756     setbuf(debugFP, NULL);
8757   }
8758             DisplayError(buf, 0);
8759             return;
8760           case ImpossibleMove:
8761             /* bug? */
8762             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8763   if (appData.debugMode) {
8764     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8765     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8766     setbuf(debugFP, NULL);
8767   }
8768             DisplayError(buf, 0);
8769             return;
8770           case EndOfFile:
8771             if (boardIndex < backwardMostMove) {
8772                 /* Oops, gap.  How did that happen? */
8773                 DisplayError(_("Gap in move list"), 0);
8774                 return;
8775             }
8776             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8777             if (boardIndex > forwardMostMove) {
8778                 forwardMostMove = boardIndex;
8779             }
8780             return;
8781           case ElapsedTime:
8782             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8783                 strcat(parseList[boardIndex-1], " ");
8784                 strcat(parseList[boardIndex-1], yy_text);
8785             }
8786             continue;
8787           case Comment:
8788           case PGNTag:
8789           case NAG:
8790           default:
8791             /* ignore */
8792             continue;
8793           case WhiteWins:
8794           case BlackWins:
8795           case GameIsDrawn:
8796           case GameUnfinished:
8797             if (gameMode == IcsExamining) {
8798                 if (boardIndex < backwardMostMove) {
8799                     /* Oops, gap.  How did that happen? */
8800                     return;
8801                 }
8802                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8803                 return;
8804             }
8805             gameInfo.result = moveType;
8806             p = strchr(yy_text, '{');
8807             if (p == NULL) p = strchr(yy_text, '(');
8808             if (p == NULL) {
8809                 p = yy_text;
8810                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8811             } else {
8812                 q = strchr(p, *p == '{' ? '}' : ')');
8813                 if (q != NULL) *q = NULLCHAR;
8814                 p++;
8815             }
8816             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8817             gameInfo.resultDetails = StrSave(p);
8818             continue;
8819         }
8820         if (boardIndex >= forwardMostMove &&
8821             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8822             backwardMostMove = blackPlaysFirst ? 1 : 0;
8823             return;
8824         }
8825         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8826                                  fromY, fromX, toY, toX, promoChar,
8827                                  parseList[boardIndex]);
8828         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8829         /* currentMoveString is set as a side-effect of yylex */
8830         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8831         strcat(moveList[boardIndex], "\n");
8832         boardIndex++;
8833         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8834         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8835           case MT_NONE:
8836           case MT_STALEMATE:
8837           default:
8838             break;
8839           case MT_CHECK:
8840             if(gameInfo.variant != VariantShogi)
8841                 strcat(parseList[boardIndex - 1], "+");
8842             break;
8843           case MT_CHECKMATE:
8844           case MT_STAINMATE:
8845             strcat(parseList[boardIndex - 1], "#");
8846             break;
8847         }
8848     }
8849 }
8850
8851
8852 /* Apply a move to the given board  */
8853 void
8854 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8855      int fromX, fromY, toX, toY;
8856      int promoChar;
8857      Board board;
8858 {
8859   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8860   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8861
8862     /* [HGM] compute & store e.p. status and castling rights for new position */
8863     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8864
8865       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8866       oldEP = (signed char)board[EP_STATUS];
8867       board[EP_STATUS] = EP_NONE;
8868
8869       if( board[toY][toX] != EmptySquare )
8870            board[EP_STATUS] = EP_CAPTURE;
8871
8872   if (fromY == DROP_RANK) {
8873         /* must be first */
8874         piece = board[toY][toX] = (ChessSquare) fromX;
8875   } else {
8876       int i;
8877
8878       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8879            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8880                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8881       } else
8882       if( board[fromY][fromX] == WhitePawn ) {
8883            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8884                board[EP_STATUS] = EP_PAWN_MOVE;
8885            if( toY-fromY==2) {
8886                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8887                         gameInfo.variant != VariantBerolina || toX < fromX)
8888                       board[EP_STATUS] = toX | berolina;
8889                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8890                         gameInfo.variant != VariantBerolina || toX > fromX)
8891                       board[EP_STATUS] = toX;
8892            }
8893       } else
8894       if( board[fromY][fromX] == BlackPawn ) {
8895            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8896                board[EP_STATUS] = EP_PAWN_MOVE;
8897            if( toY-fromY== -2) {
8898                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8899                         gameInfo.variant != VariantBerolina || toX < fromX)
8900                       board[EP_STATUS] = toX | berolina;
8901                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8902                         gameInfo.variant != VariantBerolina || toX > fromX)
8903                       board[EP_STATUS] = toX;
8904            }
8905        }
8906
8907        for(i=0; i<nrCastlingRights; i++) {
8908            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8909               board[CASTLING][i] == toX   && castlingRank[i] == toY
8910              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8911        }
8912
8913      if (fromX == toX && fromY == toY) return;
8914
8915      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8916      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8917      if(gameInfo.variant == VariantKnightmate)
8918          king += (int) WhiteUnicorn - (int) WhiteKing;
8919
8920     /* Code added by Tord: */
8921     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8922     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8923         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8924       board[fromY][fromX] = EmptySquare;
8925       board[toY][toX] = EmptySquare;
8926       if((toX > fromX) != (piece == WhiteRook)) {
8927         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8928       } else {
8929         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8930       }
8931     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8932                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8933       board[fromY][fromX] = EmptySquare;
8934       board[toY][toX] = EmptySquare;
8935       if((toX > fromX) != (piece == BlackRook)) {
8936         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8937       } else {
8938         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8939       }
8940     /* End of code added by Tord */
8941
8942     } else if (board[fromY][fromX] == king
8943         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8944         && toY == fromY && toX > fromX+1) {
8945         board[fromY][fromX] = EmptySquare;
8946         board[toY][toX] = king;
8947         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8948         board[fromY][BOARD_RGHT-1] = EmptySquare;
8949     } else if (board[fromY][fromX] == king
8950         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8951                && toY == fromY && toX < fromX-1) {
8952         board[fromY][fromX] = EmptySquare;
8953         board[toY][toX] = king;
8954         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8955         board[fromY][BOARD_LEFT] = EmptySquare;
8956     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8957                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8958                && toY >= BOARD_HEIGHT-promoRank
8959                ) {
8960         /* white pawn promotion */
8961         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8962         if (board[toY][toX] == EmptySquare) {
8963             board[toY][toX] = WhiteQueen;
8964         }
8965         if(gameInfo.variant==VariantBughouse ||
8966            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8967             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8968         board[fromY][fromX] = EmptySquare;
8969     } else if ((fromY == BOARD_HEIGHT-4)
8970                && (toX != fromX)
8971                && gameInfo.variant != VariantXiangqi
8972                && gameInfo.variant != VariantBerolina
8973                && (board[fromY][fromX] == WhitePawn)
8974                && (board[toY][toX] == EmptySquare)) {
8975         board[fromY][fromX] = EmptySquare;
8976         board[toY][toX] = WhitePawn;
8977         captured = board[toY - 1][toX];
8978         board[toY - 1][toX] = EmptySquare;
8979     } else if ((fromY == BOARD_HEIGHT-4)
8980                && (toX == fromX)
8981                && gameInfo.variant == VariantBerolina
8982                && (board[fromY][fromX] == WhitePawn)
8983                && (board[toY][toX] == EmptySquare)) {
8984         board[fromY][fromX] = EmptySquare;
8985         board[toY][toX] = WhitePawn;
8986         if(oldEP & EP_BEROLIN_A) {
8987                 captured = board[fromY][fromX-1];
8988                 board[fromY][fromX-1] = EmptySquare;
8989         }else{  captured = board[fromY][fromX+1];
8990                 board[fromY][fromX+1] = EmptySquare;
8991         }
8992     } else if (board[fromY][fromX] == king
8993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8994                && toY == fromY && toX > fromX+1) {
8995         board[fromY][fromX] = EmptySquare;
8996         board[toY][toX] = king;
8997         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8998         board[fromY][BOARD_RGHT-1] = EmptySquare;
8999     } else if (board[fromY][fromX] == king
9000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9001                && toY == fromY && toX < fromX-1) {
9002         board[fromY][fromX] = EmptySquare;
9003         board[toY][toX] = king;
9004         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9005         board[fromY][BOARD_LEFT] = EmptySquare;
9006     } else if (fromY == 7 && fromX == 3
9007                && board[fromY][fromX] == BlackKing
9008                && toY == 7 && toX == 5) {
9009         board[fromY][fromX] = EmptySquare;
9010         board[toY][toX] = BlackKing;
9011         board[fromY][7] = EmptySquare;
9012         board[toY][4] = BlackRook;
9013     } else if (fromY == 7 && fromX == 3
9014                && board[fromY][fromX] == BlackKing
9015                && toY == 7 && toX == 1) {
9016         board[fromY][fromX] = EmptySquare;
9017         board[toY][toX] = BlackKing;
9018         board[fromY][0] = EmptySquare;
9019         board[toY][2] = BlackRook;
9020     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9021                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9022                && toY < promoRank
9023                ) {
9024         /* black pawn promotion */
9025         board[toY][toX] = CharToPiece(ToLower(promoChar));
9026         if (board[toY][toX] == EmptySquare) {
9027             board[toY][toX] = BlackQueen;
9028         }
9029         if(gameInfo.variant==VariantBughouse ||
9030            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9031             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9032         board[fromY][fromX] = EmptySquare;
9033     } else if ((fromY == 3)
9034                && (toX != fromX)
9035                && gameInfo.variant != VariantXiangqi
9036                && gameInfo.variant != VariantBerolina
9037                && (board[fromY][fromX] == BlackPawn)
9038                && (board[toY][toX] == EmptySquare)) {
9039         board[fromY][fromX] = EmptySquare;
9040         board[toY][toX] = BlackPawn;
9041         captured = board[toY + 1][toX];
9042         board[toY + 1][toX] = EmptySquare;
9043     } else if ((fromY == 3)
9044                && (toX == fromX)
9045                && gameInfo.variant == VariantBerolina
9046                && (board[fromY][fromX] == BlackPawn)
9047                && (board[toY][toX] == EmptySquare)) {
9048         board[fromY][fromX] = EmptySquare;
9049         board[toY][toX] = BlackPawn;
9050         if(oldEP & EP_BEROLIN_A) {
9051                 captured = board[fromY][fromX-1];
9052                 board[fromY][fromX-1] = EmptySquare;
9053         }else{  captured = board[fromY][fromX+1];
9054                 board[fromY][fromX+1] = EmptySquare;
9055         }
9056     } else {
9057         board[toY][toX] = board[fromY][fromX];
9058         board[fromY][fromX] = EmptySquare;
9059     }
9060   }
9061
9062     if (gameInfo.holdingsWidth != 0) {
9063
9064       /* !!A lot more code needs to be written to support holdings  */
9065       /* [HGM] OK, so I have written it. Holdings are stored in the */
9066       /* penultimate board files, so they are automaticlly stored   */
9067       /* in the game history.                                       */
9068       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9069                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9070         /* Delete from holdings, by decreasing count */
9071         /* and erasing image if necessary            */
9072         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9073         if(p < (int) BlackPawn) { /* white drop */
9074              p -= (int)WhitePawn;
9075                  p = PieceToNumber((ChessSquare)p);
9076              if(p >= gameInfo.holdingsSize) p = 0;
9077              if(--board[p][BOARD_WIDTH-2] <= 0)
9078                   board[p][BOARD_WIDTH-1] = EmptySquare;
9079              if((int)board[p][BOARD_WIDTH-2] < 0)
9080                         board[p][BOARD_WIDTH-2] = 0;
9081         } else {                  /* black drop */
9082              p -= (int)BlackPawn;
9083                  p = PieceToNumber((ChessSquare)p);
9084              if(p >= gameInfo.holdingsSize) p = 0;
9085              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9086                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9087              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9088                         board[BOARD_HEIGHT-1-p][1] = 0;
9089         }
9090       }
9091       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9092           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9093         /* [HGM] holdings: Add to holdings, if holdings exist */
9094         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9095                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9096                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9097         }
9098         p = (int) captured;
9099         if (p >= (int) BlackPawn) {
9100           p -= (int)BlackPawn;
9101           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9102                   /* in Shogi restore piece to its original  first */
9103                   captured = (ChessSquare) (DEMOTED captured);
9104                   p = DEMOTED p;
9105           }
9106           p = PieceToNumber((ChessSquare)p);
9107           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9108           board[p][BOARD_WIDTH-2]++;
9109           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9110         } else {
9111           p -= (int)WhitePawn;
9112           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9113                   captured = (ChessSquare) (DEMOTED captured);
9114                   p = DEMOTED p;
9115           }
9116           p = PieceToNumber((ChessSquare)p);
9117           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9118           board[BOARD_HEIGHT-1-p][1]++;
9119           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9120         }
9121       }
9122     } else if (gameInfo.variant == VariantAtomic) {
9123       if (captured != EmptySquare) {
9124         int y, x;
9125         for (y = toY-1; y <= toY+1; y++) {
9126           for (x = toX-1; x <= toX+1; x++) {
9127             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9128                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9129               board[y][x] = EmptySquare;
9130             }
9131           }
9132         }
9133         board[toY][toX] = EmptySquare;
9134       }
9135     }
9136     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9137         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9138     } else
9139     if(promoChar == '+') {
9140         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9141         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9142     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9143         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9144     }
9145     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9146                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9147         // [HGM] superchess: take promotion piece out of holdings
9148         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9149         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9150             if(!--board[k][BOARD_WIDTH-2])
9151                 board[k][BOARD_WIDTH-1] = EmptySquare;
9152         } else {
9153             if(!--board[BOARD_HEIGHT-1-k][1])
9154                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9155         }
9156     }
9157
9158 }
9159
9160 /* Updates forwardMostMove */
9161 void
9162 MakeMove(fromX, fromY, toX, toY, promoChar)
9163      int fromX, fromY, toX, toY;
9164      int promoChar;
9165 {
9166 //    forwardMostMove++; // [HGM] bare: moved downstream
9167
9168     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9169         int timeLeft; static int lastLoadFlag=0; int king, piece;
9170         piece = boards[forwardMostMove][fromY][fromX];
9171         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9172         if(gameInfo.variant == VariantKnightmate)
9173             king += (int) WhiteUnicorn - (int) WhiteKing;
9174         if(forwardMostMove == 0) {
9175             if(blackPlaysFirst)
9176                 fprintf(serverMoves, "%s;", second.tidy);
9177             fprintf(serverMoves, "%s;", first.tidy);
9178             if(!blackPlaysFirst)
9179                 fprintf(serverMoves, "%s;", second.tidy);
9180         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9181         lastLoadFlag = loadFlag;
9182         // print base move
9183         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9184         // print castling suffix
9185         if( toY == fromY && piece == king ) {
9186             if(toX-fromX > 1)
9187                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9188             if(fromX-toX >1)
9189                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9190         }
9191         // e.p. suffix
9192         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9193              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9194              boards[forwardMostMove][toY][toX] == EmptySquare
9195              && fromX != toX && fromY != toY)
9196                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9197         // promotion suffix
9198         if(promoChar != NULLCHAR)
9199                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9200         if(!loadFlag) {
9201             fprintf(serverMoves, "/%d/%d",
9202                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9203             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9204             else                      timeLeft = blackTimeRemaining/1000;
9205             fprintf(serverMoves, "/%d", timeLeft);
9206         }
9207         fflush(serverMoves);
9208     }
9209
9210     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9211       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9212                         0, 1);
9213       return;
9214     }
9215     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9216     if (commentList[forwardMostMove+1] != NULL) {
9217         free(commentList[forwardMostMove+1]);
9218         commentList[forwardMostMove+1] = NULL;
9219     }
9220     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9221     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9222     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9223     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9224     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9225     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9226     gameInfo.result = GameUnfinished;
9227     if (gameInfo.resultDetails != NULL) {
9228         free(gameInfo.resultDetails);
9229         gameInfo.resultDetails = NULL;
9230     }
9231     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9232                               moveList[forwardMostMove - 1]);
9233     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9234                              PosFlags(forwardMostMove - 1),
9235                              fromY, fromX, toY, toX, promoChar,
9236                              parseList[forwardMostMove - 1]);
9237     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9238       case MT_NONE:
9239       case MT_STALEMATE:
9240       default:
9241         break;
9242       case MT_CHECK:
9243         if(gameInfo.variant != VariantShogi)
9244             strcat(parseList[forwardMostMove - 1], "+");
9245         break;
9246       case MT_CHECKMATE:
9247       case MT_STAINMATE:
9248         strcat(parseList[forwardMostMove - 1], "#");
9249         break;
9250     }
9251     if (appData.debugMode) {
9252         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9253     }
9254
9255 }
9256
9257 /* Updates currentMove if not pausing */
9258 void
9259 ShowMove(fromX, fromY, toX, toY)
9260 {
9261     int instant = (gameMode == PlayFromGameFile) ?
9262         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9263     if(appData.noGUI) return;
9264     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9265         if (!instant) {
9266             if (forwardMostMove == currentMove + 1) {
9267                 AnimateMove(boards[forwardMostMove - 1],
9268                             fromX, fromY, toX, toY);
9269             }
9270             if (appData.highlightLastMove) {
9271                 SetHighlights(fromX, fromY, toX, toY);
9272             }
9273         }
9274         currentMove = forwardMostMove;
9275     }
9276
9277     if (instant) return;
9278
9279     DisplayMove(currentMove - 1);
9280     DrawPosition(FALSE, boards[currentMove]);
9281     DisplayBothClocks();
9282     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9283     DisplayBook(currentMove);
9284 }
9285
9286 void SendEgtPath(ChessProgramState *cps)
9287 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9288         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9289
9290         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9291
9292         while(*p) {
9293             char c, *q = name+1, *r, *s;
9294
9295             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9296             while(*p && *p != ',') *q++ = *p++;
9297             *q++ = ':'; *q = 0;
9298             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9299                 strcmp(name, ",nalimov:") == 0 ) {
9300                 // take nalimov path from the menu-changeable option first, if it is defined
9301               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9302                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9303             } else
9304             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9305                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9306                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9307                 s = r = StrStr(s, ":") + 1; // beginning of path info
9308                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9309                 c = *r; *r = 0;             // temporarily null-terminate path info
9310                     *--q = 0;               // strip of trailig ':' from name
9311                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9312                 *r = c;
9313                 SendToProgram(buf,cps);     // send egtbpath command for this format
9314             }
9315             if(*p == ',') p++; // read away comma to position for next format name
9316         }
9317 }
9318
9319 void
9320 InitChessProgram(cps, setup)
9321      ChessProgramState *cps;
9322      int setup; /* [HGM] needed to setup FRC opening position */
9323 {
9324     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9325     if (appData.noChessProgram) return;
9326     hintRequested = FALSE;
9327     bookRequested = FALSE;
9328
9329     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9330     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9331     if(cps->memSize) { /* [HGM] memory */
9332       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9333         SendToProgram(buf, cps);
9334     }
9335     SendEgtPath(cps); /* [HGM] EGT */
9336     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9337       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9338         SendToProgram(buf, cps);
9339     }
9340
9341     SendToProgram(cps->initString, cps);
9342     if (gameInfo.variant != VariantNormal &&
9343         gameInfo.variant != VariantLoadable
9344         /* [HGM] also send variant if board size non-standard */
9345         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9346                                             ) {
9347       char *v = VariantName(gameInfo.variant);
9348       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9349         /* [HGM] in protocol 1 we have to assume all variants valid */
9350         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9351         DisplayFatalError(buf, 0, 1);
9352         return;
9353       }
9354
9355       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9356       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9357       if( gameInfo.variant == VariantXiangqi )
9358            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9359       if( gameInfo.variant == VariantShogi )
9360            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9361       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9362            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9363       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9364           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9365            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9366       if( gameInfo.variant == VariantCourier )
9367            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9368       if( gameInfo.variant == VariantSuper )
9369            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9370       if( gameInfo.variant == VariantGreat )
9371            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9372       if( gameInfo.variant == VariantSChess )
9373            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9374
9375       if(overruled) {
9376         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9377                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9378            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9379            if(StrStr(cps->variants, b) == NULL) {
9380                // specific sized variant not known, check if general sizing allowed
9381                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9382                    if(StrStr(cps->variants, "boardsize") == NULL) {
9383                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9384                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9385                        DisplayFatalError(buf, 0, 1);
9386                        return;
9387                    }
9388                    /* [HGM] here we really should compare with the maximum supported board size */
9389                }
9390            }
9391       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9392       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9393       SendToProgram(buf, cps);
9394     }
9395     currentlyInitializedVariant = gameInfo.variant;
9396
9397     /* [HGM] send opening position in FRC to first engine */
9398     if(setup) {
9399           SendToProgram("force\n", cps);
9400           SendBoard(cps, 0);
9401           /* engine is now in force mode! Set flag to wake it up after first move. */
9402           setboardSpoiledMachineBlack = 1;
9403     }
9404
9405     if (cps->sendICS) {
9406       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9407       SendToProgram(buf, cps);
9408     }
9409     cps->maybeThinking = FALSE;
9410     cps->offeredDraw = 0;
9411     if (!appData.icsActive) {
9412         SendTimeControl(cps, movesPerSession, timeControl,
9413                         timeIncrement, appData.searchDepth,
9414                         searchTime);
9415     }
9416     if (appData.showThinking
9417         // [HGM] thinking: four options require thinking output to be sent
9418         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9419                                 ) {
9420         SendToProgram("post\n", cps);
9421     }
9422     SendToProgram("hard\n", cps);
9423     if (!appData.ponderNextMove) {
9424         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9425            it without being sure what state we are in first.  "hard"
9426            is not a toggle, so that one is OK.
9427          */
9428         SendToProgram("easy\n", cps);
9429     }
9430     if (cps->usePing) {
9431       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9432       SendToProgram(buf, cps);
9433     }
9434     cps->initDone = TRUE;
9435 }
9436
9437
9438 void
9439 StartChessProgram(cps)
9440      ChessProgramState *cps;
9441 {
9442     char buf[MSG_SIZ];
9443     int err;
9444
9445     if (appData.noChessProgram) return;
9446     cps->initDone = FALSE;
9447
9448     if (strcmp(cps->host, "localhost") == 0) {
9449         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9450     } else if (*appData.remoteShell == NULLCHAR) {
9451         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9452     } else {
9453         if (*appData.remoteUser == NULLCHAR) {
9454           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9455                     cps->program);
9456         } else {
9457           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9458                     cps->host, appData.remoteUser, cps->program);
9459         }
9460         err = StartChildProcess(buf, "", &cps->pr);
9461     }
9462
9463     if (err != 0) {
9464       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9465         DisplayFatalError(buf, err, 1);
9466         cps->pr = NoProc;
9467         cps->isr = NULL;
9468         return;
9469     }
9470
9471     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9472     if (cps->protocolVersion > 1) {
9473       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9474       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9475       cps->comboCnt = 0;  //                and values of combo boxes
9476       SendToProgram(buf, cps);
9477     } else {
9478       SendToProgram("xboard\n", cps);
9479     }
9480 }
9481
9482 void
9483 TwoMachinesEventIfReady P((void))
9484 {
9485   static int curMess = 0;
9486   if (first.lastPing != first.lastPong) {
9487     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9488     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9489     return;
9490   }
9491   if (second.lastPing != second.lastPong) {
9492     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9493     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9494     return;
9495   }
9496   DisplayMessage("", ""); curMess = 0;
9497   ThawUI();
9498   TwoMachinesEvent();
9499 }
9500
9501 int
9502 CreateTourney(char *name)
9503 {
9504         FILE *f;
9505         if(name[0] == NULLCHAR) return 0;
9506         f = fopen(appData.tourneyFile, "r");
9507         if(f) { // file exists
9508             ParseArgsFromFile(f); // parse it
9509         } else {
9510             f = fopen(appData.tourneyFile, "w");
9511             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9512                 // create a file with tournament description
9513                 fprintf(f, "-participants {%s}\n", appData.participants);
9514                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9515                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9516                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9517                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9518                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9519                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9520                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9521                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9522                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9523                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9524                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9525                 if(searchTime > 0)
9526                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9527                 else {
9528                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9529                         fprintf(f, "-tc %s\n", appData.timeControl);
9530                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9531                 }
9532                 fprintf(f, "-results \"\"\n");
9533             }
9534         }
9535         fclose(f);
9536         appData.noChessProgram = FALSE;
9537         appData.clockMode = TRUE;
9538         SetGNUMode();
9539         return 1;
9540 }
9541
9542 #define MAXENGINES 1000
9543 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9544
9545 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9546 {
9547     char buf[MSG_SIZ], *p, *q;
9548     int i=1;
9549     while(*names) {
9550         p = names; q = buf;
9551         while(*p && *p != '\n') *q++ = *p++;
9552         *q = 0;
9553         if(engineList[i]) free(engineList[i]);
9554         engineList[i] = strdup(buf);
9555         if(*p == '\n') p++;
9556         TidyProgramName(engineList[i], "localhost", buf);
9557         if(engineMnemonic[i]) free(engineMnemonic[i]);
9558         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9559             strcat(buf, " (");
9560             sscanf(q + 8, "%s", buf + strlen(buf));
9561             strcat(buf, ")");
9562         }
9563         engineMnemonic[i] = strdup(buf);
9564         names = p; i++;
9565       if(i > MAXENGINES - 2) break;
9566     }
9567     engineList[i] = NULL;
9568 }
9569
9570 // following implemented as macro to avoid type limitations
9571 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9572
9573 void SwapEngines(int n)
9574 {   // swap settings for first engine and other engine (so far only some selected options)
9575     int h;
9576     char *p;
9577     if(n == 0) return;
9578     SWAP(directory, p)
9579     SWAP(chessProgram, p)
9580     SWAP(isUCI, h)
9581     SWAP(hasOwnBookUCI, h)
9582     SWAP(protocolVersion, h)
9583     SWAP(reuse, h)
9584     SWAP(scoreIsAbsolute, h)
9585     SWAP(timeOdds, h)
9586     SWAP(logo, p)
9587     SWAP(pgnName, p)
9588 }
9589
9590 void
9591 SetPlayer(int player)
9592 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9593     int i;
9594     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9595     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9596     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9597     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9598     if(mnemonic[i]) {
9599         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9600         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9601         ParseArgsFromString(buf);
9602     }
9603     free(engineName);
9604 }
9605
9606 int
9607 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9608 {   // determine players from game number
9609     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9610
9611     if(appData.tourneyType == 0) {
9612         roundsPerCycle = (nPlayers - 1) | 1;
9613         pairingsPerRound = nPlayers / 2;
9614     } else if(appData.tourneyType > 0) {
9615         roundsPerCycle = nPlayers - appData.tourneyType;
9616         pairingsPerRound = appData.tourneyType;
9617     }
9618     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9619     gamesPerCycle = gamesPerRound * roundsPerCycle;
9620     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9621     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9622     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9623     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9624     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9625     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9626
9627     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9628     if(appData.roundSync) *syncInterval = gamesPerRound;
9629
9630     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9631
9632     if(appData.tourneyType == 0) {
9633         if(curPairing == (nPlayers-1)/2 ) {
9634             *whitePlayer = curRound;
9635             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9636         } else {
9637             *whitePlayer = curRound - pairingsPerRound + curPairing;
9638             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9639             *blackPlayer = curRound + pairingsPerRound - curPairing;
9640             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9641         }
9642     } else if(appData.tourneyType > 0) {
9643         *whitePlayer = curPairing;
9644         *blackPlayer = curRound + appData.tourneyType;
9645     }
9646
9647     // take care of white/black alternation per round. 
9648     // For cycles and games this is already taken care of by default, derived from matchGame!
9649     return curRound & 1;
9650 }
9651
9652 int
9653 NextTourneyGame(int nr, int *swapColors)
9654 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9655     char *p, *q;
9656     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9657     FILE *tf;
9658     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9659     tf = fopen(appData.tourneyFile, "r");
9660     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9661     ParseArgsFromFile(tf); fclose(tf);
9662     InitTimeControls(); // TC might be altered from tourney file
9663
9664     p = appData.participants;
9665     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9666     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9667
9668     if(syncInterval) {
9669         p = q = appData.results;
9670         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9671         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9672             DisplayMessage(_("Waiting for other game(s)"),"");
9673             waitingForGame = TRUE;
9674             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9675             return 0;
9676         }
9677         waitingForGame = FALSE;
9678     }
9679
9680     if(first.pr != NoProc) return 1; // engines already loaded
9681
9682     // redefine engines, engine dir, etc.
9683     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9684     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9685     SwapEngines(1);
9686     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9687     SwapEngines(1);         // and make that valid for second engine by swapping
9688     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9689     InitEngine(&second, 1);
9690     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9691     return 1;
9692 }
9693
9694 void
9695 NextMatchGame()
9696 {   // performs game initialization that does not invoke engines, and then tries to start the game
9697     int firstWhite, swapColors = 0;
9698     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9699     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9700     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9701     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9702     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9703     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9704     Reset(FALSE, first.pr != NoProc);
9705     appData.noChessProgram = FALSE;
9706     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9707     TwoMachinesEvent();
9708 }
9709
9710 void UserAdjudicationEvent( int result )
9711 {
9712     ChessMove gameResult = GameIsDrawn;
9713
9714     if( result > 0 ) {
9715         gameResult = WhiteWins;
9716     }
9717     else if( result < 0 ) {
9718         gameResult = BlackWins;
9719     }
9720
9721     if( gameMode == TwoMachinesPlay ) {
9722         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9723     }
9724 }
9725
9726
9727 // [HGM] save: calculate checksum of game to make games easily identifiable
9728 int StringCheckSum(char *s)
9729 {
9730         int i = 0;
9731         if(s==NULL) return 0;
9732         while(*s) i = i*259 + *s++;
9733         return i;
9734 }
9735
9736 int GameCheckSum()
9737 {
9738         int i, sum=0;
9739         for(i=backwardMostMove; i<forwardMostMove; i++) {
9740                 sum += pvInfoList[i].depth;
9741                 sum += StringCheckSum(parseList[i]);
9742                 sum += StringCheckSum(commentList[i]);
9743                 sum *= 261;
9744         }
9745         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9746         return sum + StringCheckSum(commentList[i]);
9747 } // end of save patch
9748
9749 void
9750 GameEnds(result, resultDetails, whosays)
9751      ChessMove result;
9752      char *resultDetails;
9753      int whosays;
9754 {
9755     GameMode nextGameMode;
9756     int isIcsGame;
9757     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9758
9759     if(endingGame) return; /* [HGM] crash: forbid recursion */
9760     endingGame = 1;
9761     if(twoBoards) { // [HGM] dual: switch back to one board
9762         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9763         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9764     }
9765     if (appData.debugMode) {
9766       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9767               result, resultDetails ? resultDetails : "(null)", whosays);
9768     }
9769
9770     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9771
9772     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9773         /* If we are playing on ICS, the server decides when the
9774            game is over, but the engine can offer to draw, claim
9775            a draw, or resign.
9776          */
9777 #if ZIPPY
9778         if (appData.zippyPlay && first.initDone) {
9779             if (result == GameIsDrawn) {
9780                 /* In case draw still needs to be claimed */
9781                 SendToICS(ics_prefix);
9782                 SendToICS("draw\n");
9783             } else if (StrCaseStr(resultDetails, "resign")) {
9784                 SendToICS(ics_prefix);
9785                 SendToICS("resign\n");
9786             }
9787         }
9788 #endif
9789         endingGame = 0; /* [HGM] crash */
9790         return;
9791     }
9792
9793     /* If we're loading the game from a file, stop */
9794     if (whosays == GE_FILE) {
9795       (void) StopLoadGameTimer();
9796       gameFileFP = NULL;
9797     }
9798
9799     /* Cancel draw offers */
9800     first.offeredDraw = second.offeredDraw = 0;
9801
9802     /* If this is an ICS game, only ICS can really say it's done;
9803        if not, anyone can. */
9804     isIcsGame = (gameMode == IcsPlayingWhite ||
9805                  gameMode == IcsPlayingBlack ||
9806                  gameMode == IcsObserving    ||
9807                  gameMode == IcsExamining);
9808
9809     if (!isIcsGame || whosays == GE_ICS) {
9810         /* OK -- not an ICS game, or ICS said it was done */
9811         StopClocks();
9812         if (!isIcsGame && !appData.noChessProgram)
9813           SetUserThinkingEnables();
9814
9815         /* [HGM] if a machine claims the game end we verify this claim */
9816         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9817             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9818                 char claimer;
9819                 ChessMove trueResult = (ChessMove) -1;
9820
9821                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9822                                             first.twoMachinesColor[0] :
9823                                             second.twoMachinesColor[0] ;
9824
9825                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9826                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9827                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9828                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9829                 } else
9830                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9831                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9832                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9833                 } else
9834                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9835                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9836                 }
9837
9838                 // now verify win claims, but not in drop games, as we don't understand those yet
9839                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9840                                                  || gameInfo.variant == VariantGreat) &&
9841                     (result == WhiteWins && claimer == 'w' ||
9842                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9843                       if (appData.debugMode) {
9844                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9845                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9846                       }
9847                       if(result != trueResult) {
9848                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9849                               result = claimer == 'w' ? BlackWins : WhiteWins;
9850                               resultDetails = buf;
9851                       }
9852                 } else
9853                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9854                     && (forwardMostMove <= backwardMostMove ||
9855                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9856                         (claimer=='b')==(forwardMostMove&1))
9857                                                                                   ) {
9858                       /* [HGM] verify: draws that were not flagged are false claims */
9859                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9860                       result = claimer == 'w' ? BlackWins : WhiteWins;
9861                       resultDetails = buf;
9862                 }
9863                 /* (Claiming a loss is accepted no questions asked!) */
9864             }
9865             /* [HGM] bare: don't allow bare King to win */
9866             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9867                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9868                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9869                && result != GameIsDrawn)
9870             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9871                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9872                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9873                         if(p >= 0 && p <= (int)WhiteKing) k++;
9874                 }
9875                 if (appData.debugMode) {
9876                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9877                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9878                 }
9879                 if(k <= 1) {
9880                         result = GameIsDrawn;
9881                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9882                         resultDetails = buf;
9883                 }
9884             }
9885         }
9886
9887
9888         if(serverMoves != NULL && !loadFlag) { char c = '=';
9889             if(result==WhiteWins) c = '+';
9890             if(result==BlackWins) c = '-';
9891             if(resultDetails != NULL)
9892                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9893         }
9894         if (resultDetails != NULL) {
9895             gameInfo.result = result;
9896             gameInfo.resultDetails = StrSave(resultDetails);
9897
9898             /* display last move only if game was not loaded from file */
9899             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9900                 DisplayMove(currentMove - 1);
9901
9902             if (forwardMostMove != 0) {
9903                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9904                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9905                                                                 ) {
9906                     if (*appData.saveGameFile != NULLCHAR) {
9907                         SaveGameToFile(appData.saveGameFile, TRUE);
9908                     } else if (appData.autoSaveGames) {
9909                         AutoSaveGame();
9910                     }
9911                     if (*appData.savePositionFile != NULLCHAR) {
9912                         SavePositionToFile(appData.savePositionFile);
9913                     }
9914                 }
9915             }
9916
9917             /* Tell program how game ended in case it is learning */
9918             /* [HGM] Moved this to after saving the PGN, just in case */
9919             /* engine died and we got here through time loss. In that */
9920             /* case we will get a fatal error writing the pipe, which */
9921             /* would otherwise lose us the PGN.                       */
9922             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9923             /* output during GameEnds should never be fatal anymore   */
9924             if (gameMode == MachinePlaysWhite ||
9925                 gameMode == MachinePlaysBlack ||
9926                 gameMode == TwoMachinesPlay ||
9927                 gameMode == IcsPlayingWhite ||
9928                 gameMode == IcsPlayingBlack ||
9929                 gameMode == BeginningOfGame) {
9930                 char buf[MSG_SIZ];
9931                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9932                         resultDetails);
9933                 if (first.pr != NoProc) {
9934                     SendToProgram(buf, &first);
9935                 }
9936                 if (second.pr != NoProc &&
9937                     gameMode == TwoMachinesPlay) {
9938                     SendToProgram(buf, &second);
9939                 }
9940             }
9941         }
9942
9943         if (appData.icsActive) {
9944             if (appData.quietPlay &&
9945                 (gameMode == IcsPlayingWhite ||
9946                  gameMode == IcsPlayingBlack)) {
9947                 SendToICS(ics_prefix);
9948                 SendToICS("set shout 1\n");
9949             }
9950             nextGameMode = IcsIdle;
9951             ics_user_moved = FALSE;
9952             /* clean up premove.  It's ugly when the game has ended and the
9953              * premove highlights are still on the board.
9954              */
9955             if (gotPremove) {
9956               gotPremove = FALSE;
9957               ClearPremoveHighlights();
9958               DrawPosition(FALSE, boards[currentMove]);
9959             }
9960             if (whosays == GE_ICS) {
9961                 switch (result) {
9962                 case WhiteWins:
9963                     if (gameMode == IcsPlayingWhite)
9964                         PlayIcsWinSound();
9965                     else if(gameMode == IcsPlayingBlack)
9966                         PlayIcsLossSound();
9967                     break;
9968                 case BlackWins:
9969                     if (gameMode == IcsPlayingBlack)
9970                         PlayIcsWinSound();
9971                     else if(gameMode == IcsPlayingWhite)
9972                         PlayIcsLossSound();
9973                     break;
9974                 case GameIsDrawn:
9975                     PlayIcsDrawSound();
9976                     break;
9977                 default:
9978                     PlayIcsUnfinishedSound();
9979                 }
9980             }
9981         } else if (gameMode == EditGame ||
9982                    gameMode == PlayFromGameFile ||
9983                    gameMode == AnalyzeMode ||
9984                    gameMode == AnalyzeFile) {
9985             nextGameMode = gameMode;
9986         } else {
9987             nextGameMode = EndOfGame;
9988         }
9989         pausing = FALSE;
9990         ModeHighlight();
9991     } else {
9992         nextGameMode = gameMode;
9993     }
9994
9995     if (appData.noChessProgram) {
9996         gameMode = nextGameMode;
9997         ModeHighlight();
9998         endingGame = 0; /* [HGM] crash */
9999         return;
10000     }
10001
10002     if (first.reuse) {
10003         /* Put first chess program into idle state */
10004         if (first.pr != NoProc &&
10005             (gameMode == MachinePlaysWhite ||
10006              gameMode == MachinePlaysBlack ||
10007              gameMode == TwoMachinesPlay ||
10008              gameMode == IcsPlayingWhite ||
10009              gameMode == IcsPlayingBlack ||
10010              gameMode == BeginningOfGame)) {
10011             SendToProgram("force\n", &first);
10012             if (first.usePing) {
10013               char buf[MSG_SIZ];
10014               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10015               SendToProgram(buf, &first);
10016             }
10017         }
10018     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10019         /* Kill off first chess program */
10020         if (first.isr != NULL)
10021           RemoveInputSource(first.isr);
10022         first.isr = NULL;
10023
10024         if (first.pr != NoProc) {
10025             ExitAnalyzeMode();
10026             DoSleep( appData.delayBeforeQuit );
10027             SendToProgram("quit\n", &first);
10028             DoSleep( appData.delayAfterQuit );
10029             DestroyChildProcess(first.pr, first.useSigterm);
10030         }
10031         first.pr = NoProc;
10032     }
10033     if (second.reuse) {
10034         /* Put second chess program into idle state */
10035         if (second.pr != NoProc &&
10036             gameMode == TwoMachinesPlay) {
10037             SendToProgram("force\n", &second);
10038             if (second.usePing) {
10039               char buf[MSG_SIZ];
10040               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10041               SendToProgram(buf, &second);
10042             }
10043         }
10044     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10045         /* Kill off second chess program */
10046         if (second.isr != NULL)
10047           RemoveInputSource(second.isr);
10048         second.isr = NULL;
10049
10050         if (second.pr != NoProc) {
10051             DoSleep( appData.delayBeforeQuit );
10052             SendToProgram("quit\n", &second);
10053             DoSleep( appData.delayAfterQuit );
10054             DestroyChildProcess(second.pr, second.useSigterm);
10055         }
10056         second.pr = NoProc;
10057     }
10058
10059     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10060         char resChar = '=';
10061         switch (result) {
10062         case WhiteWins:
10063           resChar = '+';
10064           if (first.twoMachinesColor[0] == 'w') {
10065             first.matchWins++;
10066           } else {
10067             second.matchWins++;
10068           }
10069           break;
10070         case BlackWins:
10071           resChar = '-';
10072           if (first.twoMachinesColor[0] == 'b') {
10073             first.matchWins++;
10074           } else {
10075             second.matchWins++;
10076           }
10077           break;
10078         case GameUnfinished:
10079           resChar = ' ';
10080         default:
10081           break;
10082         }
10083
10084         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10085         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10086             ReserveGame(nextGame, resChar); // sets nextGame
10087             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10088         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10089
10090         if (nextGame <= appData.matchGames && !abortMatch) {
10091             gameMode = nextGameMode;
10092             matchGame = nextGame; // this will be overruled in tourney mode!
10093             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10094             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10095             endingGame = 0; /* [HGM] crash */
10096             return;
10097         } else {
10098             gameMode = nextGameMode;
10099             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10100                      first.tidy, second.tidy,
10101                      first.matchWins, second.matchWins,
10102                      appData.matchGames - (first.matchWins + second.matchWins));
10103             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10104             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10105                 first.twoMachinesColor = "black\n";
10106                 second.twoMachinesColor = "white\n";
10107             } else {
10108                 first.twoMachinesColor = "white\n";
10109                 second.twoMachinesColor = "black\n";
10110             }
10111         }
10112     }
10113     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10114         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10115       ExitAnalyzeMode();
10116     gameMode = nextGameMode;
10117     ModeHighlight();
10118     endingGame = 0;  /* [HGM] crash */
10119     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10120         if(matchMode == TRUE) { // match through command line: exit with or without popup
10121             if(ranking) {
10122                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10123                 else ExitEvent(0);
10124             } else DisplayFatalError(buf, 0, 0);
10125         } else { // match through menu; just stop, with or without popup
10126             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10127             if(ranking){
10128                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10129             } else DisplayNote(buf);
10130       }
10131       if(ranking) free(ranking);
10132     }
10133 }
10134
10135 /* Assumes program was just initialized (initString sent).
10136    Leaves program in force mode. */
10137 void
10138 FeedMovesToProgram(cps, upto)
10139      ChessProgramState *cps;
10140      int upto;
10141 {
10142     int i;
10143
10144     if (appData.debugMode)
10145       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10146               startedFromSetupPosition ? "position and " : "",
10147               backwardMostMove, upto, cps->which);
10148     if(currentlyInitializedVariant != gameInfo.variant) {
10149       char buf[MSG_SIZ];
10150         // [HGM] variantswitch: make engine aware of new variant
10151         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10152                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10153         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10154         SendToProgram(buf, cps);
10155         currentlyInitializedVariant = gameInfo.variant;
10156     }
10157     SendToProgram("force\n", cps);
10158     if (startedFromSetupPosition) {
10159         SendBoard(cps, backwardMostMove);
10160     if (appData.debugMode) {
10161         fprintf(debugFP, "feedMoves\n");
10162     }
10163     }
10164     for (i = backwardMostMove; i < upto; i++) {
10165         SendMoveToProgram(i, cps);
10166     }
10167 }
10168
10169
10170 int
10171 ResurrectChessProgram()
10172 {
10173      /* The chess program may have exited.
10174         If so, restart it and feed it all the moves made so far. */
10175     static int doInit = 0;
10176
10177     if (appData.noChessProgram) return 1;
10178
10179     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10180         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10181         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10182         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10183     } else {
10184         if (first.pr != NoProc) return 1;
10185         StartChessProgram(&first);
10186     }
10187     InitChessProgram(&first, FALSE);
10188     FeedMovesToProgram(&first, currentMove);
10189
10190     if (!first.sendTime) {
10191         /* can't tell gnuchess what its clock should read,
10192            so we bow to its notion. */
10193         ResetClocks();
10194         timeRemaining[0][currentMove] = whiteTimeRemaining;
10195         timeRemaining[1][currentMove] = blackTimeRemaining;
10196     }
10197
10198     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10199                 appData.icsEngineAnalyze) && first.analysisSupport) {
10200       SendToProgram("analyze\n", &first);
10201       first.analyzing = TRUE;
10202     }
10203     return 1;
10204 }
10205
10206 /*
10207  * Button procedures
10208  */
10209 void
10210 Reset(redraw, init)
10211      int redraw, init;
10212 {
10213     int i;
10214
10215     if (appData.debugMode) {
10216         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10217                 redraw, init, gameMode);
10218     }
10219     CleanupTail(); // [HGM] vari: delete any stored variations
10220     pausing = pauseExamInvalid = FALSE;
10221     startedFromSetupPosition = blackPlaysFirst = FALSE;
10222     firstMove = TRUE;
10223     whiteFlag = blackFlag = FALSE;
10224     userOfferedDraw = FALSE;
10225     hintRequested = bookRequested = FALSE;
10226     first.maybeThinking = FALSE;
10227     second.maybeThinking = FALSE;
10228     first.bookSuspend = FALSE; // [HGM] book
10229     second.bookSuspend = FALSE;
10230     thinkOutput[0] = NULLCHAR;
10231     lastHint[0] = NULLCHAR;
10232     ClearGameInfo(&gameInfo);
10233     gameInfo.variant = StringToVariant(appData.variant);
10234     ics_user_moved = ics_clock_paused = FALSE;
10235     ics_getting_history = H_FALSE;
10236     ics_gamenum = -1;
10237     white_holding[0] = black_holding[0] = NULLCHAR;
10238     ClearProgramStats();
10239     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10240
10241     ResetFrontEnd();
10242     ClearHighlights();
10243     flipView = appData.flipView;
10244     ClearPremoveHighlights();
10245     gotPremove = FALSE;
10246     alarmSounded = FALSE;
10247
10248     GameEnds(EndOfFile, NULL, GE_PLAYER);
10249     if(appData.serverMovesName != NULL) {
10250         /* [HGM] prepare to make moves file for broadcasting */
10251         clock_t t = clock();
10252         if(serverMoves != NULL) fclose(serverMoves);
10253         serverMoves = fopen(appData.serverMovesName, "r");
10254         if(serverMoves != NULL) {
10255             fclose(serverMoves);
10256             /* delay 15 sec before overwriting, so all clients can see end */
10257             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10258         }
10259         serverMoves = fopen(appData.serverMovesName, "w");
10260     }
10261
10262     ExitAnalyzeMode();
10263     gameMode = BeginningOfGame;
10264     ModeHighlight();
10265     if(appData.icsActive) gameInfo.variant = VariantNormal;
10266     currentMove = forwardMostMove = backwardMostMove = 0;
10267     InitPosition(redraw);
10268     for (i = 0; i < MAX_MOVES; i++) {
10269         if (commentList[i] != NULL) {
10270             free(commentList[i]);
10271             commentList[i] = NULL;
10272         }
10273     }
10274     ResetClocks();
10275     timeRemaining[0][0] = whiteTimeRemaining;
10276     timeRemaining[1][0] = blackTimeRemaining;
10277
10278     if (first.pr == NULL) {
10279         StartChessProgram(&first);
10280     }
10281     if (init) {
10282             InitChessProgram(&first, startedFromSetupPosition);
10283     }
10284     DisplayTitle("");
10285     DisplayMessage("", "");
10286     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10287     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10288 }
10289
10290 void
10291 AutoPlayGameLoop()
10292 {
10293     for (;;) {
10294         if (!AutoPlayOneMove())
10295           return;
10296         if (matchMode || appData.timeDelay == 0)
10297           continue;
10298         if (appData.timeDelay < 0)
10299           return;
10300         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10301         break;
10302     }
10303 }
10304
10305
10306 int
10307 AutoPlayOneMove()
10308 {
10309     int fromX, fromY, toX, toY;
10310
10311     if (appData.debugMode) {
10312       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10313     }
10314
10315     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10316       return FALSE;
10317
10318     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10319       pvInfoList[currentMove].depth = programStats.depth;
10320       pvInfoList[currentMove].score = programStats.score;
10321       pvInfoList[currentMove].time  = 0;
10322       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10323     }
10324
10325     if (currentMove >= forwardMostMove) {
10326       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10327       gameMode = EditGame;
10328       ModeHighlight();
10329
10330       /* [AS] Clear current move marker at the end of a game */
10331       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10332
10333       return FALSE;
10334     }
10335
10336     toX = moveList[currentMove][2] - AAA;
10337     toY = moveList[currentMove][3] - ONE;
10338
10339     if (moveList[currentMove][1] == '@') {
10340         if (appData.highlightLastMove) {
10341             SetHighlights(-1, -1, toX, toY);
10342         }
10343     } else {
10344         fromX = moveList[currentMove][0] - AAA;
10345         fromY = moveList[currentMove][1] - ONE;
10346
10347         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10348
10349         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10350
10351         if (appData.highlightLastMove) {
10352             SetHighlights(fromX, fromY, toX, toY);
10353         }
10354     }
10355     DisplayMove(currentMove);
10356     SendMoveToProgram(currentMove++, &first);
10357     DisplayBothClocks();
10358     DrawPosition(FALSE, boards[currentMove]);
10359     // [HGM] PV info: always display, routine tests if empty
10360     DisplayComment(currentMove - 1, commentList[currentMove]);
10361     return TRUE;
10362 }
10363
10364
10365 int
10366 LoadGameOneMove(readAhead)
10367      ChessMove readAhead;
10368 {
10369     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10370     char promoChar = NULLCHAR;
10371     ChessMove moveType;
10372     char move[MSG_SIZ];
10373     char *p, *q;
10374
10375     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10376         gameMode != AnalyzeMode && gameMode != Training) {
10377         gameFileFP = NULL;
10378         return FALSE;
10379     }
10380
10381     yyboardindex = forwardMostMove;
10382     if (readAhead != EndOfFile) {
10383       moveType = readAhead;
10384     } else {
10385       if (gameFileFP == NULL)
10386           return FALSE;
10387       moveType = (ChessMove) Myylex();
10388     }
10389
10390     done = FALSE;
10391     switch (moveType) {
10392       case Comment:
10393         if (appData.debugMode)
10394           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10395         p = yy_text;
10396
10397         /* append the comment but don't display it */
10398         AppendComment(currentMove, p, FALSE);
10399         return TRUE;
10400
10401       case WhiteCapturesEnPassant:
10402       case BlackCapturesEnPassant:
10403       case WhitePromotion:
10404       case BlackPromotion:
10405       case WhiteNonPromotion:
10406       case BlackNonPromotion:
10407       case NormalMove:
10408       case WhiteKingSideCastle:
10409       case WhiteQueenSideCastle:
10410       case BlackKingSideCastle:
10411       case BlackQueenSideCastle:
10412       case WhiteKingSideCastleWild:
10413       case WhiteQueenSideCastleWild:
10414       case BlackKingSideCastleWild:
10415       case BlackQueenSideCastleWild:
10416       /* PUSH Fabien */
10417       case WhiteHSideCastleFR:
10418       case WhiteASideCastleFR:
10419       case BlackHSideCastleFR:
10420       case BlackASideCastleFR:
10421       /* POP Fabien */
10422         if (appData.debugMode)
10423           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10424         fromX = currentMoveString[0] - AAA;
10425         fromY = currentMoveString[1] - ONE;
10426         toX = currentMoveString[2] - AAA;
10427         toY = currentMoveString[3] - ONE;
10428         promoChar = currentMoveString[4];
10429         break;
10430
10431       case WhiteDrop:
10432       case BlackDrop:
10433         if (appData.debugMode)
10434           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10435         fromX = moveType == WhiteDrop ?
10436           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10437         (int) CharToPiece(ToLower(currentMoveString[0]));
10438         fromY = DROP_RANK;
10439         toX = currentMoveString[2] - AAA;
10440         toY = currentMoveString[3] - ONE;
10441         break;
10442
10443       case WhiteWins:
10444       case BlackWins:
10445       case GameIsDrawn:
10446       case GameUnfinished:
10447         if (appData.debugMode)
10448           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10449         p = strchr(yy_text, '{');
10450         if (p == NULL) p = strchr(yy_text, '(');
10451         if (p == NULL) {
10452             p = yy_text;
10453             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10454         } else {
10455             q = strchr(p, *p == '{' ? '}' : ')');
10456             if (q != NULL) *q = NULLCHAR;
10457             p++;
10458         }
10459         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10460         GameEnds(moveType, p, GE_FILE);
10461         done = TRUE;
10462         if (cmailMsgLoaded) {
10463             ClearHighlights();
10464             flipView = WhiteOnMove(currentMove);
10465             if (moveType == GameUnfinished) flipView = !flipView;
10466             if (appData.debugMode)
10467               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10468         }
10469         break;
10470
10471       case EndOfFile:
10472         if (appData.debugMode)
10473           fprintf(debugFP, "Parser hit end of file\n");
10474         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10475           case MT_NONE:
10476           case MT_CHECK:
10477             break;
10478           case MT_CHECKMATE:
10479           case MT_STAINMATE:
10480             if (WhiteOnMove(currentMove)) {
10481                 GameEnds(BlackWins, "Black mates", GE_FILE);
10482             } else {
10483                 GameEnds(WhiteWins, "White mates", GE_FILE);
10484             }
10485             break;
10486           case MT_STALEMATE:
10487             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10488             break;
10489         }
10490         done = TRUE;
10491         break;
10492
10493       case MoveNumberOne:
10494         if (lastLoadGameStart == GNUChessGame) {
10495             /* GNUChessGames have numbers, but they aren't move numbers */
10496             if (appData.debugMode)
10497               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10498                       yy_text, (int) moveType);
10499             return LoadGameOneMove(EndOfFile); /* tail recursion */
10500         }
10501         /* else fall thru */
10502
10503       case XBoardGame:
10504       case GNUChessGame:
10505       case PGNTag:
10506         /* Reached start of next game in file */
10507         if (appData.debugMode)
10508           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10509         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10510           case MT_NONE:
10511           case MT_CHECK:
10512             break;
10513           case MT_CHECKMATE:
10514           case MT_STAINMATE:
10515             if (WhiteOnMove(currentMove)) {
10516                 GameEnds(BlackWins, "Black mates", GE_FILE);
10517             } else {
10518                 GameEnds(WhiteWins, "White mates", GE_FILE);
10519             }
10520             break;
10521           case MT_STALEMATE:
10522             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10523             break;
10524         }
10525         done = TRUE;
10526         break;
10527
10528       case PositionDiagram:     /* should not happen; ignore */
10529       case ElapsedTime:         /* ignore */
10530       case NAG:                 /* ignore */
10531         if (appData.debugMode)
10532           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10533                   yy_text, (int) moveType);
10534         return LoadGameOneMove(EndOfFile); /* tail recursion */
10535
10536       case IllegalMove:
10537         if (appData.testLegality) {
10538             if (appData.debugMode)
10539               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10540             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10541                     (forwardMostMove / 2) + 1,
10542                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10543             DisplayError(move, 0);
10544             done = TRUE;
10545         } else {
10546             if (appData.debugMode)
10547               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10548                       yy_text, currentMoveString);
10549             fromX = currentMoveString[0] - AAA;
10550             fromY = currentMoveString[1] - ONE;
10551             toX = currentMoveString[2] - AAA;
10552             toY = currentMoveString[3] - ONE;
10553             promoChar = currentMoveString[4];
10554         }
10555         break;
10556
10557       case AmbiguousMove:
10558         if (appData.debugMode)
10559           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10560         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10561                 (forwardMostMove / 2) + 1,
10562                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10563         DisplayError(move, 0);
10564         done = TRUE;
10565         break;
10566
10567       default:
10568       case ImpossibleMove:
10569         if (appData.debugMode)
10570           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10571         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10572                 (forwardMostMove / 2) + 1,
10573                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10574         DisplayError(move, 0);
10575         done = TRUE;
10576         break;
10577     }
10578
10579     if (done) {
10580         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10581             DrawPosition(FALSE, boards[currentMove]);
10582             DisplayBothClocks();
10583             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10584               DisplayComment(currentMove - 1, commentList[currentMove]);
10585         }
10586         (void) StopLoadGameTimer();
10587         gameFileFP = NULL;
10588         cmailOldMove = forwardMostMove;
10589         return FALSE;
10590     } else {
10591         /* currentMoveString is set as a side-effect of yylex */
10592
10593         thinkOutput[0] = NULLCHAR;
10594         MakeMove(fromX, fromY, toX, toY, promoChar);
10595         currentMove = forwardMostMove;
10596         return TRUE;
10597     }
10598 }
10599
10600 /* Load the nth game from the given file */
10601 int
10602 LoadGameFromFile(filename, n, title, useList)
10603      char *filename;
10604      int n;
10605      char *title;
10606      /*Boolean*/ int useList;
10607 {
10608     FILE *f;
10609     char buf[MSG_SIZ];
10610
10611     if (strcmp(filename, "-") == 0) {
10612         f = stdin;
10613         title = "stdin";
10614     } else {
10615         f = fopen(filename, "rb");
10616         if (f == NULL) {
10617           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10618             DisplayError(buf, errno);
10619             return FALSE;
10620         }
10621     }
10622     if (fseek(f, 0, 0) == -1) {
10623         /* f is not seekable; probably a pipe */
10624         useList = FALSE;
10625     }
10626     if (useList && n == 0) {
10627         int error = GameListBuild(f);
10628         if (error) {
10629             DisplayError(_("Cannot build game list"), error);
10630         } else if (!ListEmpty(&gameList) &&
10631                    ((ListGame *) gameList.tailPred)->number > 1) {
10632             GameListPopUp(f, title);
10633             return TRUE;
10634         }
10635         GameListDestroy();
10636         n = 1;
10637     }
10638     if (n == 0) n = 1;
10639     return LoadGame(f, n, title, FALSE);
10640 }
10641
10642
10643 void
10644 MakeRegisteredMove()
10645 {
10646     int fromX, fromY, toX, toY;
10647     char promoChar;
10648     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10649         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10650           case CMAIL_MOVE:
10651           case CMAIL_DRAW:
10652             if (appData.debugMode)
10653               fprintf(debugFP, "Restoring %s for game %d\n",
10654                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10655
10656             thinkOutput[0] = NULLCHAR;
10657             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10658             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10659             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10660             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10661             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10662             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10663             MakeMove(fromX, fromY, toX, toY, promoChar);
10664             ShowMove(fromX, fromY, toX, toY);
10665
10666             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10667               case MT_NONE:
10668               case MT_CHECK:
10669                 break;
10670
10671               case MT_CHECKMATE:
10672               case MT_STAINMATE:
10673                 if (WhiteOnMove(currentMove)) {
10674                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10675                 } else {
10676                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10677                 }
10678                 break;
10679
10680               case MT_STALEMATE:
10681                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10682                 break;
10683             }
10684
10685             break;
10686
10687           case CMAIL_RESIGN:
10688             if (WhiteOnMove(currentMove)) {
10689                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10690             } else {
10691                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10692             }
10693             break;
10694
10695           case CMAIL_ACCEPT:
10696             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10697             break;
10698
10699           default:
10700             break;
10701         }
10702     }
10703
10704     return;
10705 }
10706
10707 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10708 int
10709 CmailLoadGame(f, gameNumber, title, useList)
10710      FILE *f;
10711      int gameNumber;
10712      char *title;
10713      int useList;
10714 {
10715     int retVal;
10716
10717     if (gameNumber > nCmailGames) {
10718         DisplayError(_("No more games in this message"), 0);
10719         return FALSE;
10720     }
10721     if (f == lastLoadGameFP) {
10722         int offset = gameNumber - lastLoadGameNumber;
10723         if (offset == 0) {
10724             cmailMsg[0] = NULLCHAR;
10725             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10726                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10727                 nCmailMovesRegistered--;
10728             }
10729             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10730             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10731                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10732             }
10733         } else {
10734             if (! RegisterMove()) return FALSE;
10735         }
10736     }
10737
10738     retVal = LoadGame(f, gameNumber, title, useList);
10739
10740     /* Make move registered during previous look at this game, if any */
10741     MakeRegisteredMove();
10742
10743     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10744         commentList[currentMove]
10745           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10746         DisplayComment(currentMove - 1, commentList[currentMove]);
10747     }
10748
10749     return retVal;
10750 }
10751
10752 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10753 int
10754 ReloadGame(offset)
10755      int offset;
10756 {
10757     int gameNumber = lastLoadGameNumber + offset;
10758     if (lastLoadGameFP == NULL) {
10759         DisplayError(_("No game has been loaded yet"), 0);
10760         return FALSE;
10761     }
10762     if (gameNumber <= 0) {
10763         DisplayError(_("Can't back up any further"), 0);
10764         return FALSE;
10765     }
10766     if (cmailMsgLoaded) {
10767         return CmailLoadGame(lastLoadGameFP, gameNumber,
10768                              lastLoadGameTitle, lastLoadGameUseList);
10769     } else {
10770         return LoadGame(lastLoadGameFP, gameNumber,
10771                         lastLoadGameTitle, lastLoadGameUseList);
10772     }
10773 }
10774
10775
10776
10777 /* Load the nth game from open file f */
10778 int
10779 LoadGame(f, gameNumber, title, useList)
10780      FILE *f;
10781      int gameNumber;
10782      char *title;
10783      int useList;
10784 {
10785     ChessMove cm;
10786     char buf[MSG_SIZ];
10787     int gn = gameNumber;
10788     ListGame *lg = NULL;
10789     int numPGNTags = 0;
10790     int err;
10791     GameMode oldGameMode;
10792     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10793
10794     if (appData.debugMode)
10795         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10796
10797     if (gameMode == Training )
10798         SetTrainingModeOff();
10799
10800     oldGameMode = gameMode;
10801     if (gameMode != BeginningOfGame) {
10802       Reset(FALSE, TRUE);
10803     }
10804
10805     gameFileFP = f;
10806     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10807         fclose(lastLoadGameFP);
10808     }
10809
10810     if (useList) {
10811         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10812
10813         if (lg) {
10814             fseek(f, lg->offset, 0);
10815             GameListHighlight(gameNumber);
10816             gn = 1;
10817         }
10818         else {
10819             DisplayError(_("Game number out of range"), 0);
10820             return FALSE;
10821         }
10822     } else {
10823         GameListDestroy();
10824         if (fseek(f, 0, 0) == -1) {
10825             if (f == lastLoadGameFP ?
10826                 gameNumber == lastLoadGameNumber + 1 :
10827                 gameNumber == 1) {
10828                 gn = 1;
10829             } else {
10830                 DisplayError(_("Can't seek on game file"), 0);
10831                 return FALSE;
10832             }
10833         }
10834     }
10835     lastLoadGameFP = f;
10836     lastLoadGameNumber = gameNumber;
10837     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10838     lastLoadGameUseList = useList;
10839
10840     yynewfile(f);
10841
10842     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10843       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10844                 lg->gameInfo.black);
10845             DisplayTitle(buf);
10846     } else if (*title != NULLCHAR) {
10847         if (gameNumber > 1) {
10848           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10849             DisplayTitle(buf);
10850         } else {
10851             DisplayTitle(title);
10852         }
10853     }
10854
10855     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10856         gameMode = PlayFromGameFile;
10857         ModeHighlight();
10858     }
10859
10860     currentMove = forwardMostMove = backwardMostMove = 0;
10861     CopyBoard(boards[0], initialPosition);
10862     StopClocks();
10863
10864     /*
10865      * Skip the first gn-1 games in the file.
10866      * Also skip over anything that precedes an identifiable
10867      * start of game marker, to avoid being confused by
10868      * garbage at the start of the file.  Currently
10869      * recognized start of game markers are the move number "1",
10870      * the pattern "gnuchess .* game", the pattern
10871      * "^[#;%] [^ ]* game file", and a PGN tag block.
10872      * A game that starts with one of the latter two patterns
10873      * will also have a move number 1, possibly
10874      * following a position diagram.
10875      * 5-4-02: Let's try being more lenient and allowing a game to
10876      * start with an unnumbered move.  Does that break anything?
10877      */
10878     cm = lastLoadGameStart = EndOfFile;
10879     while (gn > 0) {
10880         yyboardindex = forwardMostMove;
10881         cm = (ChessMove) Myylex();
10882         switch (cm) {
10883           case EndOfFile:
10884             if (cmailMsgLoaded) {
10885                 nCmailGames = CMAIL_MAX_GAMES - gn;
10886             } else {
10887                 Reset(TRUE, TRUE);
10888                 DisplayError(_("Game not found in file"), 0);
10889             }
10890             return FALSE;
10891
10892           case GNUChessGame:
10893           case XBoardGame:
10894             gn--;
10895             lastLoadGameStart = cm;
10896             break;
10897
10898           case MoveNumberOne:
10899             switch (lastLoadGameStart) {
10900               case GNUChessGame:
10901               case XBoardGame:
10902               case PGNTag:
10903                 break;
10904               case MoveNumberOne:
10905               case EndOfFile:
10906                 gn--;           /* count this game */
10907                 lastLoadGameStart = cm;
10908                 break;
10909               default:
10910                 /* impossible */
10911                 break;
10912             }
10913             break;
10914
10915           case PGNTag:
10916             switch (lastLoadGameStart) {
10917               case GNUChessGame:
10918               case PGNTag:
10919               case MoveNumberOne:
10920               case EndOfFile:
10921                 gn--;           /* count this game */
10922                 lastLoadGameStart = cm;
10923                 break;
10924               case XBoardGame:
10925                 lastLoadGameStart = cm; /* game counted already */
10926                 break;
10927               default:
10928                 /* impossible */
10929                 break;
10930             }
10931             if (gn > 0) {
10932                 do {
10933                     yyboardindex = forwardMostMove;
10934                     cm = (ChessMove) Myylex();
10935                 } while (cm == PGNTag || cm == Comment);
10936             }
10937             break;
10938
10939           case WhiteWins:
10940           case BlackWins:
10941           case GameIsDrawn:
10942             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10943                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10944                     != CMAIL_OLD_RESULT) {
10945                     nCmailResults ++ ;
10946                     cmailResult[  CMAIL_MAX_GAMES
10947                                 - gn - 1] = CMAIL_OLD_RESULT;
10948                 }
10949             }
10950             break;
10951
10952           case NormalMove:
10953             /* Only a NormalMove can be at the start of a game
10954              * without a position diagram. */
10955             if (lastLoadGameStart == EndOfFile ) {
10956               gn--;
10957               lastLoadGameStart = MoveNumberOne;
10958             }
10959             break;
10960
10961           default:
10962             break;
10963         }
10964     }
10965
10966     if (appData.debugMode)
10967       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10968
10969     if (cm == XBoardGame) {
10970         /* Skip any header junk before position diagram and/or move 1 */
10971         for (;;) {
10972             yyboardindex = forwardMostMove;
10973             cm = (ChessMove) Myylex();
10974
10975             if (cm == EndOfFile ||
10976                 cm == GNUChessGame || cm == XBoardGame) {
10977                 /* Empty game; pretend end-of-file and handle later */
10978                 cm = EndOfFile;
10979                 break;
10980             }
10981
10982             if (cm == MoveNumberOne || cm == PositionDiagram ||
10983                 cm == PGNTag || cm == Comment)
10984               break;
10985         }
10986     } else if (cm == GNUChessGame) {
10987         if (gameInfo.event != NULL) {
10988             free(gameInfo.event);
10989         }
10990         gameInfo.event = StrSave(yy_text);
10991     }
10992
10993     startedFromSetupPosition = FALSE;
10994     while (cm == PGNTag) {
10995         if (appData.debugMode)
10996           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10997         err = ParsePGNTag(yy_text, &gameInfo);
10998         if (!err) numPGNTags++;
10999
11000         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11001         if(gameInfo.variant != oldVariant) {
11002             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11003             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11004             InitPosition(TRUE);
11005             oldVariant = gameInfo.variant;
11006             if (appData.debugMode)
11007               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11008         }
11009
11010
11011         if (gameInfo.fen != NULL) {
11012           Board initial_position;
11013           startedFromSetupPosition = TRUE;
11014           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11015             Reset(TRUE, TRUE);
11016             DisplayError(_("Bad FEN position in file"), 0);
11017             return FALSE;
11018           }
11019           CopyBoard(boards[0], initial_position);
11020           if (blackPlaysFirst) {
11021             currentMove = forwardMostMove = backwardMostMove = 1;
11022             CopyBoard(boards[1], initial_position);
11023             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11024             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11025             timeRemaining[0][1] = whiteTimeRemaining;
11026             timeRemaining[1][1] = blackTimeRemaining;
11027             if (commentList[0] != NULL) {
11028               commentList[1] = commentList[0];
11029               commentList[0] = NULL;
11030             }
11031           } else {
11032             currentMove = forwardMostMove = backwardMostMove = 0;
11033           }
11034           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11035           {   int i;
11036               initialRulePlies = FENrulePlies;
11037               for( i=0; i< nrCastlingRights; i++ )
11038                   initialRights[i] = initial_position[CASTLING][i];
11039           }
11040           yyboardindex = forwardMostMove;
11041           free(gameInfo.fen);
11042           gameInfo.fen = NULL;
11043         }
11044
11045         yyboardindex = forwardMostMove;
11046         cm = (ChessMove) Myylex();
11047
11048         /* Handle comments interspersed among the tags */
11049         while (cm == Comment) {
11050             char *p;
11051             if (appData.debugMode)
11052               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11053             p = yy_text;
11054             AppendComment(currentMove, p, FALSE);
11055             yyboardindex = forwardMostMove;
11056             cm = (ChessMove) Myylex();
11057         }
11058     }
11059
11060     /* don't rely on existence of Event tag since if game was
11061      * pasted from clipboard the Event tag may not exist
11062      */
11063     if (numPGNTags > 0){
11064         char *tags;
11065         if (gameInfo.variant == VariantNormal) {
11066           VariantClass v = StringToVariant(gameInfo.event);
11067           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11068           if(v < VariantShogi) gameInfo.variant = v;
11069         }
11070         if (!matchMode) {
11071           if( appData.autoDisplayTags ) {
11072             tags = PGNTags(&gameInfo);
11073             TagsPopUp(tags, CmailMsg());
11074             free(tags);
11075           }
11076         }
11077     } else {
11078         /* Make something up, but don't display it now */
11079         SetGameInfo();
11080         TagsPopDown();
11081     }
11082
11083     if (cm == PositionDiagram) {
11084         int i, j;
11085         char *p;
11086         Board initial_position;
11087
11088         if (appData.debugMode)
11089           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11090
11091         if (!startedFromSetupPosition) {
11092             p = yy_text;
11093             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11094               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11095                 switch (*p) {
11096                   case '{':
11097                   case '[':
11098                   case '-':
11099                   case ' ':
11100                   case '\t':
11101                   case '\n':
11102                   case '\r':
11103                     break;
11104                   default:
11105                     initial_position[i][j++] = CharToPiece(*p);
11106                     break;
11107                 }
11108             while (*p == ' ' || *p == '\t' ||
11109                    *p == '\n' || *p == '\r') p++;
11110
11111             if (strncmp(p, "black", strlen("black"))==0)
11112               blackPlaysFirst = TRUE;
11113             else
11114               blackPlaysFirst = FALSE;
11115             startedFromSetupPosition = TRUE;
11116
11117             CopyBoard(boards[0], initial_position);
11118             if (blackPlaysFirst) {
11119                 currentMove = forwardMostMove = backwardMostMove = 1;
11120                 CopyBoard(boards[1], initial_position);
11121                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11122                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11123                 timeRemaining[0][1] = whiteTimeRemaining;
11124                 timeRemaining[1][1] = blackTimeRemaining;
11125                 if (commentList[0] != NULL) {
11126                     commentList[1] = commentList[0];
11127                     commentList[0] = NULL;
11128                 }
11129             } else {
11130                 currentMove = forwardMostMove = backwardMostMove = 0;
11131             }
11132         }
11133         yyboardindex = forwardMostMove;
11134         cm = (ChessMove) Myylex();
11135     }
11136
11137     if (first.pr == NoProc) {
11138         StartChessProgram(&first);
11139     }
11140     InitChessProgram(&first, FALSE);
11141     SendToProgram("force\n", &first);
11142     if (startedFromSetupPosition) {
11143         SendBoard(&first, forwardMostMove);
11144     if (appData.debugMode) {
11145         fprintf(debugFP, "Load Game\n");
11146     }
11147         DisplayBothClocks();
11148     }
11149
11150     /* [HGM] server: flag to write setup moves in broadcast file as one */
11151     loadFlag = appData.suppressLoadMoves;
11152
11153     while (cm == Comment) {
11154         char *p;
11155         if (appData.debugMode)
11156           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11157         p = yy_text;
11158         AppendComment(currentMove, p, FALSE);
11159         yyboardindex = forwardMostMove;
11160         cm = (ChessMove) Myylex();
11161     }
11162
11163     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11164         cm == WhiteWins || cm == BlackWins ||
11165         cm == GameIsDrawn || cm == GameUnfinished) {
11166         DisplayMessage("", _("No moves in game"));
11167         if (cmailMsgLoaded) {
11168             if (appData.debugMode)
11169               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11170             ClearHighlights();
11171             flipView = FALSE;
11172         }
11173         DrawPosition(FALSE, boards[currentMove]);
11174         DisplayBothClocks();
11175         gameMode = EditGame;
11176         ModeHighlight();
11177         gameFileFP = NULL;
11178         cmailOldMove = 0;
11179         return TRUE;
11180     }
11181
11182     // [HGM] PV info: routine tests if comment empty
11183     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11184         DisplayComment(currentMove - 1, commentList[currentMove]);
11185     }
11186     if (!matchMode && appData.timeDelay != 0)
11187       DrawPosition(FALSE, boards[currentMove]);
11188
11189     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11190       programStats.ok_to_send = 1;
11191     }
11192
11193     /* if the first token after the PGN tags is a move
11194      * and not move number 1, retrieve it from the parser
11195      */
11196     if (cm != MoveNumberOne)
11197         LoadGameOneMove(cm);
11198
11199     /* load the remaining moves from the file */
11200     while (LoadGameOneMove(EndOfFile)) {
11201       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11202       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11203     }
11204
11205     /* rewind to the start of the game */
11206     currentMove = backwardMostMove;
11207
11208     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11209
11210     if (oldGameMode == AnalyzeFile ||
11211         oldGameMode == AnalyzeMode) {
11212       AnalyzeFileEvent();
11213     }
11214
11215     if (matchMode || appData.timeDelay == 0) {
11216       ToEndEvent();
11217       gameMode = EditGame;
11218       ModeHighlight();
11219     } else if (appData.timeDelay > 0) {
11220       AutoPlayGameLoop();
11221     }
11222
11223     if (appData.debugMode)
11224         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11225
11226     loadFlag = 0; /* [HGM] true game starts */
11227     return TRUE;
11228 }
11229
11230 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11231 int
11232 ReloadPosition(offset)
11233      int offset;
11234 {
11235     int positionNumber = lastLoadPositionNumber + offset;
11236     if (lastLoadPositionFP == NULL) {
11237         DisplayError(_("No position has been loaded yet"), 0);
11238         return FALSE;
11239     }
11240     if (positionNumber <= 0) {
11241         DisplayError(_("Can't back up any further"), 0);
11242         return FALSE;
11243     }
11244     return LoadPosition(lastLoadPositionFP, positionNumber,
11245                         lastLoadPositionTitle);
11246 }
11247
11248 /* Load the nth position from the given file */
11249 int
11250 LoadPositionFromFile(filename, n, title)
11251      char *filename;
11252      int n;
11253      char *title;
11254 {
11255     FILE *f;
11256     char buf[MSG_SIZ];
11257
11258     if (strcmp(filename, "-") == 0) {
11259         return LoadPosition(stdin, n, "stdin");
11260     } else {
11261         f = fopen(filename, "rb");
11262         if (f == NULL) {
11263             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11264             DisplayError(buf, errno);
11265             return FALSE;
11266         } else {
11267             return LoadPosition(f, n, title);
11268         }
11269     }
11270 }
11271
11272 /* Load the nth position from the given open file, and close it */
11273 int
11274 LoadPosition(f, positionNumber, title)
11275      FILE *f;
11276      int positionNumber;
11277      char *title;
11278 {
11279     char *p, line[MSG_SIZ];
11280     Board initial_position;
11281     int i, j, fenMode, pn;
11282
11283     if (gameMode == Training )
11284         SetTrainingModeOff();
11285
11286     if (gameMode != BeginningOfGame) {
11287         Reset(FALSE, TRUE);
11288     }
11289     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11290         fclose(lastLoadPositionFP);
11291     }
11292     if (positionNumber == 0) positionNumber = 1;
11293     lastLoadPositionFP = f;
11294     lastLoadPositionNumber = positionNumber;
11295     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11296     if (first.pr == NoProc) {
11297       StartChessProgram(&first);
11298       InitChessProgram(&first, FALSE);
11299     }
11300     pn = positionNumber;
11301     if (positionNumber < 0) {
11302         /* Negative position number means to seek to that byte offset */
11303         if (fseek(f, -positionNumber, 0) == -1) {
11304             DisplayError(_("Can't seek on position file"), 0);
11305             return FALSE;
11306         };
11307         pn = 1;
11308     } else {
11309         if (fseek(f, 0, 0) == -1) {
11310             if (f == lastLoadPositionFP ?
11311                 positionNumber == lastLoadPositionNumber + 1 :
11312                 positionNumber == 1) {
11313                 pn = 1;
11314             } else {
11315                 DisplayError(_("Can't seek on position file"), 0);
11316                 return FALSE;
11317             }
11318         }
11319     }
11320     /* See if this file is FEN or old-style xboard */
11321     if (fgets(line, MSG_SIZ, f) == NULL) {
11322         DisplayError(_("Position not found in file"), 0);
11323         return FALSE;
11324     }
11325     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11326     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11327
11328     if (pn >= 2) {
11329         if (fenMode || line[0] == '#') pn--;
11330         while (pn > 0) {
11331             /* skip positions before number pn */
11332             if (fgets(line, MSG_SIZ, f) == NULL) {
11333                 Reset(TRUE, TRUE);
11334                 DisplayError(_("Position not found in file"), 0);
11335                 return FALSE;
11336             }
11337             if (fenMode || line[0] == '#') pn--;
11338         }
11339     }
11340
11341     if (fenMode) {
11342         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11343             DisplayError(_("Bad FEN position in file"), 0);
11344             return FALSE;
11345         }
11346     } else {
11347         (void) fgets(line, MSG_SIZ, f);
11348         (void) fgets(line, MSG_SIZ, f);
11349
11350         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11351             (void) fgets(line, MSG_SIZ, f);
11352             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11353                 if (*p == ' ')
11354                   continue;
11355                 initial_position[i][j++] = CharToPiece(*p);
11356             }
11357         }
11358
11359         blackPlaysFirst = FALSE;
11360         if (!feof(f)) {
11361             (void) fgets(line, MSG_SIZ, f);
11362             if (strncmp(line, "black", strlen("black"))==0)
11363               blackPlaysFirst = TRUE;
11364         }
11365     }
11366     startedFromSetupPosition = TRUE;
11367
11368     SendToProgram("force\n", &first);
11369     CopyBoard(boards[0], initial_position);
11370     if (blackPlaysFirst) {
11371         currentMove = forwardMostMove = backwardMostMove = 1;
11372         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11373         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11374         CopyBoard(boards[1], initial_position);
11375         DisplayMessage("", _("Black to play"));
11376     } else {
11377         currentMove = forwardMostMove = backwardMostMove = 0;
11378         DisplayMessage("", _("White to play"));
11379     }
11380     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11381     SendBoard(&first, forwardMostMove);
11382     if (appData.debugMode) {
11383 int i, j;
11384   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11385   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11386         fprintf(debugFP, "Load Position\n");
11387     }
11388
11389     if (positionNumber > 1) {
11390       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11391         DisplayTitle(line);
11392     } else {
11393         DisplayTitle(title);
11394     }
11395     gameMode = EditGame;
11396     ModeHighlight();
11397     ResetClocks();
11398     timeRemaining[0][1] = whiteTimeRemaining;
11399     timeRemaining[1][1] = blackTimeRemaining;
11400     DrawPosition(FALSE, boards[currentMove]);
11401
11402     return TRUE;
11403 }
11404
11405
11406 void
11407 CopyPlayerNameIntoFileName(dest, src)
11408      char **dest, *src;
11409 {
11410     while (*src != NULLCHAR && *src != ',') {
11411         if (*src == ' ') {
11412             *(*dest)++ = '_';
11413             src++;
11414         } else {
11415             *(*dest)++ = *src++;
11416         }
11417     }
11418 }
11419
11420 char *DefaultFileName(ext)
11421      char *ext;
11422 {
11423     static char def[MSG_SIZ];
11424     char *p;
11425
11426     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11427         p = def;
11428         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11429         *p++ = '-';
11430         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11431         *p++ = '.';
11432         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11433     } else {
11434         def[0] = NULLCHAR;
11435     }
11436     return def;
11437 }
11438
11439 /* Save the current game to the given file */
11440 int
11441 SaveGameToFile(filename, append)
11442      char *filename;
11443      int append;
11444 {
11445     FILE *f;
11446     char buf[MSG_SIZ];
11447     int result;
11448
11449     if (strcmp(filename, "-") == 0) {
11450         return SaveGame(stdout, 0, NULL);
11451     } else {
11452         f = fopen(filename, append ? "a" : "w");
11453         if (f == NULL) {
11454             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11455             DisplayError(buf, errno);
11456             return FALSE;
11457         } else {
11458             safeStrCpy(buf, lastMsg, MSG_SIZ);
11459             DisplayMessage(_("Waiting for access to save file"), "");
11460             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11461             DisplayMessage(_("Saving game"), "");
11462             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11463             result = SaveGame(f, 0, NULL);
11464             DisplayMessage(buf, "");
11465             return result;
11466         }
11467     }
11468 }
11469
11470 char *
11471 SavePart(str)
11472      char *str;
11473 {
11474     static char buf[MSG_SIZ];
11475     char *p;
11476
11477     p = strchr(str, ' ');
11478     if (p == NULL) return str;
11479     strncpy(buf, str, p - str);
11480     buf[p - str] = NULLCHAR;
11481     return buf;
11482 }
11483
11484 #define PGN_MAX_LINE 75
11485
11486 #define PGN_SIDE_WHITE  0
11487 #define PGN_SIDE_BLACK  1
11488
11489 /* [AS] */
11490 static int FindFirstMoveOutOfBook( int side )
11491 {
11492     int result = -1;
11493
11494     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11495         int index = backwardMostMove;
11496         int has_book_hit = 0;
11497
11498         if( (index % 2) != side ) {
11499             index++;
11500         }
11501
11502         while( index < forwardMostMove ) {
11503             /* Check to see if engine is in book */
11504             int depth = pvInfoList[index].depth;
11505             int score = pvInfoList[index].score;
11506             int in_book = 0;
11507
11508             if( depth <= 2 ) {
11509                 in_book = 1;
11510             }
11511             else if( score == 0 && depth == 63 ) {
11512                 in_book = 1; /* Zappa */
11513             }
11514             else if( score == 2 && depth == 99 ) {
11515                 in_book = 1; /* Abrok */
11516             }
11517
11518             has_book_hit += in_book;
11519
11520             if( ! in_book ) {
11521                 result = index;
11522
11523                 break;
11524             }
11525
11526             index += 2;
11527         }
11528     }
11529
11530     return result;
11531 }
11532
11533 /* [AS] */
11534 void GetOutOfBookInfo( char * buf )
11535 {
11536     int oob[2];
11537     int i;
11538     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11539
11540     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11541     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11542
11543     *buf = '\0';
11544
11545     if( oob[0] >= 0 || oob[1] >= 0 ) {
11546         for( i=0; i<2; i++ ) {
11547             int idx = oob[i];
11548
11549             if( idx >= 0 ) {
11550                 if( i > 0 && oob[0] >= 0 ) {
11551                     strcat( buf, "   " );
11552                 }
11553
11554                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11555                 sprintf( buf+strlen(buf), "%s%.2f",
11556                     pvInfoList[idx].score >= 0 ? "+" : "",
11557                     pvInfoList[idx].score / 100.0 );
11558             }
11559         }
11560     }
11561 }
11562
11563 /* Save game in PGN style and close the file */
11564 int
11565 SaveGamePGN(f)
11566      FILE *f;
11567 {
11568     int i, offset, linelen, newblock;
11569     time_t tm;
11570 //    char *movetext;
11571     char numtext[32];
11572     int movelen, numlen, blank;
11573     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11574
11575     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11576
11577     tm = time((time_t *) NULL);
11578
11579     PrintPGNTags(f, &gameInfo);
11580
11581     if (backwardMostMove > 0 || startedFromSetupPosition) {
11582         char *fen = PositionToFEN(backwardMostMove, NULL);
11583         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11584         fprintf(f, "\n{--------------\n");
11585         PrintPosition(f, backwardMostMove);
11586         fprintf(f, "--------------}\n");
11587         free(fen);
11588     }
11589     else {
11590         /* [AS] Out of book annotation */
11591         if( appData.saveOutOfBookInfo ) {
11592             char buf[64];
11593
11594             GetOutOfBookInfo( buf );
11595
11596             if( buf[0] != '\0' ) {
11597                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11598             }
11599         }
11600
11601         fprintf(f, "\n");
11602     }
11603
11604     i = backwardMostMove;
11605     linelen = 0;
11606     newblock = TRUE;
11607
11608     while (i < forwardMostMove) {
11609         /* Print comments preceding this move */
11610         if (commentList[i] != NULL) {
11611             if (linelen > 0) fprintf(f, "\n");
11612             fprintf(f, "%s", commentList[i]);
11613             linelen = 0;
11614             newblock = TRUE;
11615         }
11616
11617         /* Format move number */
11618         if ((i % 2) == 0)
11619           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11620         else
11621           if (newblock)
11622             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11623           else
11624             numtext[0] = NULLCHAR;
11625
11626         numlen = strlen(numtext);
11627         newblock = FALSE;
11628
11629         /* Print move number */
11630         blank = linelen > 0 && numlen > 0;
11631         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11632             fprintf(f, "\n");
11633             linelen = 0;
11634             blank = 0;
11635         }
11636         if (blank) {
11637             fprintf(f, " ");
11638             linelen++;
11639         }
11640         fprintf(f, "%s", numtext);
11641         linelen += numlen;
11642
11643         /* Get move */
11644         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11645         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11646
11647         /* Print move */
11648         blank = linelen > 0 && movelen > 0;
11649         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11650             fprintf(f, "\n");
11651             linelen = 0;
11652             blank = 0;
11653         }
11654         if (blank) {
11655             fprintf(f, " ");
11656             linelen++;
11657         }
11658         fprintf(f, "%s", move_buffer);
11659         linelen += movelen;
11660
11661         /* [AS] Add PV info if present */
11662         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11663             /* [HGM] add time */
11664             char buf[MSG_SIZ]; int seconds;
11665
11666             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11667
11668             if( seconds <= 0)
11669               buf[0] = 0;
11670             else
11671               if( seconds < 30 )
11672                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11673               else
11674                 {
11675                   seconds = (seconds + 4)/10; // round to full seconds
11676                   if( seconds < 60 )
11677                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11678                   else
11679                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11680                 }
11681
11682             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11683                       pvInfoList[i].score >= 0 ? "+" : "",
11684                       pvInfoList[i].score / 100.0,
11685                       pvInfoList[i].depth,
11686                       buf );
11687
11688             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11689
11690             /* Print score/depth */
11691             blank = linelen > 0 && movelen > 0;
11692             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11693                 fprintf(f, "\n");
11694                 linelen = 0;
11695                 blank = 0;
11696             }
11697             if (blank) {
11698                 fprintf(f, " ");
11699                 linelen++;
11700             }
11701             fprintf(f, "%s", move_buffer);
11702             linelen += movelen;
11703         }
11704
11705         i++;
11706     }
11707
11708     /* Start a new line */
11709     if (linelen > 0) fprintf(f, "\n");
11710
11711     /* Print comments after last move */
11712     if (commentList[i] != NULL) {
11713         fprintf(f, "%s\n", commentList[i]);
11714     }
11715
11716     /* Print result */
11717     if (gameInfo.resultDetails != NULL &&
11718         gameInfo.resultDetails[0] != NULLCHAR) {
11719         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11720                 PGNResult(gameInfo.result));
11721     } else {
11722         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11723     }
11724
11725     fclose(f);
11726     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11727     return TRUE;
11728 }
11729
11730 /* Save game in old style and close the file */
11731 int
11732 SaveGameOldStyle(f)
11733      FILE *f;
11734 {
11735     int i, offset;
11736     time_t tm;
11737
11738     tm = time((time_t *) NULL);
11739
11740     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11741     PrintOpponents(f);
11742
11743     if (backwardMostMove > 0 || startedFromSetupPosition) {
11744         fprintf(f, "\n[--------------\n");
11745         PrintPosition(f, backwardMostMove);
11746         fprintf(f, "--------------]\n");
11747     } else {
11748         fprintf(f, "\n");
11749     }
11750
11751     i = backwardMostMove;
11752     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11753
11754     while (i < forwardMostMove) {
11755         if (commentList[i] != NULL) {
11756             fprintf(f, "[%s]\n", commentList[i]);
11757         }
11758
11759         if ((i % 2) == 1) {
11760             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11761             i++;
11762         } else {
11763             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11764             i++;
11765             if (commentList[i] != NULL) {
11766                 fprintf(f, "\n");
11767                 continue;
11768             }
11769             if (i >= forwardMostMove) {
11770                 fprintf(f, "\n");
11771                 break;
11772             }
11773             fprintf(f, "%s\n", parseList[i]);
11774             i++;
11775         }
11776     }
11777
11778     if (commentList[i] != NULL) {
11779         fprintf(f, "[%s]\n", commentList[i]);
11780     }
11781
11782     /* This isn't really the old style, but it's close enough */
11783     if (gameInfo.resultDetails != NULL &&
11784         gameInfo.resultDetails[0] != NULLCHAR) {
11785         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11786                 gameInfo.resultDetails);
11787     } else {
11788         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11789     }
11790
11791     fclose(f);
11792     return TRUE;
11793 }
11794
11795 /* Save the current game to open file f and close the file */
11796 int
11797 SaveGame(f, dummy, dummy2)
11798      FILE *f;
11799      int dummy;
11800      char *dummy2;
11801 {
11802     if (gameMode == EditPosition) EditPositionDone(TRUE);
11803     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11804     if (appData.oldSaveStyle)
11805       return SaveGameOldStyle(f);
11806     else
11807       return SaveGamePGN(f);
11808 }
11809
11810 /* Save the current position to the given file */
11811 int
11812 SavePositionToFile(filename)
11813      char *filename;
11814 {
11815     FILE *f;
11816     char buf[MSG_SIZ];
11817
11818     if (strcmp(filename, "-") == 0) {
11819         return SavePosition(stdout, 0, NULL);
11820     } else {
11821         f = fopen(filename, "a");
11822         if (f == NULL) {
11823             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11824             DisplayError(buf, errno);
11825             return FALSE;
11826         } else {
11827             safeStrCpy(buf, lastMsg, MSG_SIZ);
11828             DisplayMessage(_("Waiting for access to save file"), "");
11829             flock(fileno(f), LOCK_EX); // [HGM] lock
11830             DisplayMessage(_("Saving position"), "");
11831             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11832             SavePosition(f, 0, NULL);
11833             DisplayMessage(buf, "");
11834             return TRUE;
11835         }
11836     }
11837 }
11838
11839 /* Save the current position to the given open file and close the file */
11840 int
11841 SavePosition(f, dummy, dummy2)
11842      FILE *f;
11843      int dummy;
11844      char *dummy2;
11845 {
11846     time_t tm;
11847     char *fen;
11848
11849     if (gameMode == EditPosition) EditPositionDone(TRUE);
11850     if (appData.oldSaveStyle) {
11851         tm = time((time_t *) NULL);
11852
11853         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11854         PrintOpponents(f);
11855         fprintf(f, "[--------------\n");
11856         PrintPosition(f, currentMove);
11857         fprintf(f, "--------------]\n");
11858     } else {
11859         fen = PositionToFEN(currentMove, NULL);
11860         fprintf(f, "%s\n", fen);
11861         free(fen);
11862     }
11863     fclose(f);
11864     return TRUE;
11865 }
11866
11867 void
11868 ReloadCmailMsgEvent(unregister)
11869      int unregister;
11870 {
11871 #if !WIN32
11872     static char *inFilename = NULL;
11873     static char *outFilename;
11874     int i;
11875     struct stat inbuf, outbuf;
11876     int status;
11877
11878     /* Any registered moves are unregistered if unregister is set, */
11879     /* i.e. invoked by the signal handler */
11880     if (unregister) {
11881         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11882             cmailMoveRegistered[i] = FALSE;
11883             if (cmailCommentList[i] != NULL) {
11884                 free(cmailCommentList[i]);
11885                 cmailCommentList[i] = NULL;
11886             }
11887         }
11888         nCmailMovesRegistered = 0;
11889     }
11890
11891     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11892         cmailResult[i] = CMAIL_NOT_RESULT;
11893     }
11894     nCmailResults = 0;
11895
11896     if (inFilename == NULL) {
11897         /* Because the filenames are static they only get malloced once  */
11898         /* and they never get freed                                      */
11899         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11900         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11901
11902         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11903         sprintf(outFilename, "%s.out", appData.cmailGameName);
11904     }
11905
11906     status = stat(outFilename, &outbuf);
11907     if (status < 0) {
11908         cmailMailedMove = FALSE;
11909     } else {
11910         status = stat(inFilename, &inbuf);
11911         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11912     }
11913
11914     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11915        counts the games, notes how each one terminated, etc.
11916
11917        It would be nice to remove this kludge and instead gather all
11918        the information while building the game list.  (And to keep it
11919        in the game list nodes instead of having a bunch of fixed-size
11920        parallel arrays.)  Note this will require getting each game's
11921        termination from the PGN tags, as the game list builder does
11922        not process the game moves.  --mann
11923        */
11924     cmailMsgLoaded = TRUE;
11925     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11926
11927     /* Load first game in the file or popup game menu */
11928     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11929
11930 #endif /* !WIN32 */
11931     return;
11932 }
11933
11934 int
11935 RegisterMove()
11936 {
11937     FILE *f;
11938     char string[MSG_SIZ];
11939
11940     if (   cmailMailedMove
11941         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11942         return TRUE;            /* Allow free viewing  */
11943     }
11944
11945     /* Unregister move to ensure that we don't leave RegisterMove        */
11946     /* with the move registered when the conditions for registering no   */
11947     /* longer hold                                                       */
11948     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11949         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11950         nCmailMovesRegistered --;
11951
11952         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11953           {
11954               free(cmailCommentList[lastLoadGameNumber - 1]);
11955               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11956           }
11957     }
11958
11959     if (cmailOldMove == -1) {
11960         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11961         return FALSE;
11962     }
11963
11964     if (currentMove > cmailOldMove + 1) {
11965         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11966         return FALSE;
11967     }
11968
11969     if (currentMove < cmailOldMove) {
11970         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11971         return FALSE;
11972     }
11973
11974     if (forwardMostMove > currentMove) {
11975         /* Silently truncate extra moves */
11976         TruncateGame();
11977     }
11978
11979     if (   (currentMove == cmailOldMove + 1)
11980         || (   (currentMove == cmailOldMove)
11981             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11982                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11983         if (gameInfo.result != GameUnfinished) {
11984             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11985         }
11986
11987         if (commentList[currentMove] != NULL) {
11988             cmailCommentList[lastLoadGameNumber - 1]
11989               = StrSave(commentList[currentMove]);
11990         }
11991         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11992
11993         if (appData.debugMode)
11994           fprintf(debugFP, "Saving %s for game %d\n",
11995                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11996
11997         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11998
11999         f = fopen(string, "w");
12000         if (appData.oldSaveStyle) {
12001             SaveGameOldStyle(f); /* also closes the file */
12002
12003             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12004             f = fopen(string, "w");
12005             SavePosition(f, 0, NULL); /* also closes the file */
12006         } else {
12007             fprintf(f, "{--------------\n");
12008             PrintPosition(f, currentMove);
12009             fprintf(f, "--------------}\n\n");
12010
12011             SaveGame(f, 0, NULL); /* also closes the file*/
12012         }
12013
12014         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12015         nCmailMovesRegistered ++;
12016     } else if (nCmailGames == 1) {
12017         DisplayError(_("You have not made a move yet"), 0);
12018         return FALSE;
12019     }
12020
12021     return TRUE;
12022 }
12023
12024 void
12025 MailMoveEvent()
12026 {
12027 #if !WIN32
12028     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12029     FILE *commandOutput;
12030     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12031     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12032     int nBuffers;
12033     int i;
12034     int archived;
12035     char *arcDir;
12036
12037     if (! cmailMsgLoaded) {
12038         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12039         return;
12040     }
12041
12042     if (nCmailGames == nCmailResults) {
12043         DisplayError(_("No unfinished games"), 0);
12044         return;
12045     }
12046
12047 #if CMAIL_PROHIBIT_REMAIL
12048     if (cmailMailedMove) {
12049       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);
12050         DisplayError(msg, 0);
12051         return;
12052     }
12053 #endif
12054
12055     if (! (cmailMailedMove || RegisterMove())) return;
12056
12057     if (   cmailMailedMove
12058         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12059       snprintf(string, MSG_SIZ, partCommandString,
12060                appData.debugMode ? " -v" : "", appData.cmailGameName);
12061         commandOutput = popen(string, "r");
12062
12063         if (commandOutput == NULL) {
12064             DisplayError(_("Failed to invoke cmail"), 0);
12065         } else {
12066             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12067                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12068             }
12069             if (nBuffers > 1) {
12070                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12071                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12072                 nBytes = MSG_SIZ - 1;
12073             } else {
12074                 (void) memcpy(msg, buffer, nBytes);
12075             }
12076             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12077
12078             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12079                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12080
12081                 archived = TRUE;
12082                 for (i = 0; i < nCmailGames; i ++) {
12083                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12084                         archived = FALSE;
12085                     }
12086                 }
12087                 if (   archived
12088                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12089                         != NULL)) {
12090                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12091                            arcDir,
12092                            appData.cmailGameName,
12093                            gameInfo.date);
12094                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12095                     cmailMsgLoaded = FALSE;
12096                 }
12097             }
12098
12099             DisplayInformation(msg);
12100             pclose(commandOutput);
12101         }
12102     } else {
12103         if ((*cmailMsg) != '\0') {
12104             DisplayInformation(cmailMsg);
12105         }
12106     }
12107
12108     return;
12109 #endif /* !WIN32 */
12110 }
12111
12112 char *
12113 CmailMsg()
12114 {
12115 #if WIN32
12116     return NULL;
12117 #else
12118     int  prependComma = 0;
12119     char number[5];
12120     char string[MSG_SIZ];       /* Space for game-list */
12121     int  i;
12122
12123     if (!cmailMsgLoaded) return "";
12124
12125     if (cmailMailedMove) {
12126       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12127     } else {
12128         /* Create a list of games left */
12129       snprintf(string, MSG_SIZ, "[");
12130         for (i = 0; i < nCmailGames; i ++) {
12131             if (! (   cmailMoveRegistered[i]
12132                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12133                 if (prependComma) {
12134                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12135                 } else {
12136                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12137                     prependComma = 1;
12138                 }
12139
12140                 strcat(string, number);
12141             }
12142         }
12143         strcat(string, "]");
12144
12145         if (nCmailMovesRegistered + nCmailResults == 0) {
12146             switch (nCmailGames) {
12147               case 1:
12148                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12149                 break;
12150
12151               case 2:
12152                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12153                 break;
12154
12155               default:
12156                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12157                          nCmailGames);
12158                 break;
12159             }
12160         } else {
12161             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12162               case 1:
12163                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12164                          string);
12165                 break;
12166
12167               case 0:
12168                 if (nCmailResults == nCmailGames) {
12169                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12170                 } else {
12171                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12172                 }
12173                 break;
12174
12175               default:
12176                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12177                          string);
12178             }
12179         }
12180     }
12181     return cmailMsg;
12182 #endif /* WIN32 */
12183 }
12184
12185 void
12186 ResetGameEvent()
12187 {
12188     if (gameMode == Training)
12189       SetTrainingModeOff();
12190
12191     Reset(TRUE, TRUE);
12192     cmailMsgLoaded = FALSE;
12193     if (appData.icsActive) {
12194       SendToICS(ics_prefix);
12195       SendToICS("refresh\n");
12196     }
12197 }
12198
12199 void
12200 ExitEvent(status)
12201      int status;
12202 {
12203     exiting++;
12204     if (exiting > 2) {
12205       /* Give up on clean exit */
12206       exit(status);
12207     }
12208     if (exiting > 1) {
12209       /* Keep trying for clean exit */
12210       return;
12211     }
12212
12213     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12214
12215     if (telnetISR != NULL) {
12216       RemoveInputSource(telnetISR);
12217     }
12218     if (icsPR != NoProc) {
12219       DestroyChildProcess(icsPR, TRUE);
12220     }
12221
12222     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12223     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12224
12225     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12226     /* make sure this other one finishes before killing it!                  */
12227     if(endingGame) { int count = 0;
12228         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12229         while(endingGame && count++ < 10) DoSleep(1);
12230         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12231     }
12232
12233     /* Kill off chess programs */
12234     if (first.pr != NoProc) {
12235         ExitAnalyzeMode();
12236
12237         DoSleep( appData.delayBeforeQuit );
12238         SendToProgram("quit\n", &first);
12239         DoSleep( appData.delayAfterQuit );
12240         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12241     }
12242     if (second.pr != NoProc) {
12243         DoSleep( appData.delayBeforeQuit );
12244         SendToProgram("quit\n", &second);
12245         DoSleep( appData.delayAfterQuit );
12246         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12247     }
12248     if (first.isr != NULL) {
12249         RemoveInputSource(first.isr);
12250     }
12251     if (second.isr != NULL) {
12252         RemoveInputSource(second.isr);
12253     }
12254
12255     ShutDownFrontEnd();
12256     exit(status);
12257 }
12258
12259 void
12260 PauseEvent()
12261 {
12262     if (appData.debugMode)
12263         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12264     if (pausing) {
12265         pausing = FALSE;
12266         ModeHighlight();
12267         if (gameMode == MachinePlaysWhite ||
12268             gameMode == MachinePlaysBlack) {
12269             StartClocks();
12270         } else {
12271             DisplayBothClocks();
12272         }
12273         if (gameMode == PlayFromGameFile) {
12274             if (appData.timeDelay >= 0)
12275                 AutoPlayGameLoop();
12276         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12277             Reset(FALSE, TRUE);
12278             SendToICS(ics_prefix);
12279             SendToICS("refresh\n");
12280         } else if (currentMove < forwardMostMove) {
12281             ForwardInner(forwardMostMove);
12282         }
12283         pauseExamInvalid = FALSE;
12284     } else {
12285         switch (gameMode) {
12286           default:
12287             return;
12288           case IcsExamining:
12289             pauseExamForwardMostMove = forwardMostMove;
12290             pauseExamInvalid = FALSE;
12291             /* fall through */
12292           case IcsObserving:
12293           case IcsPlayingWhite:
12294           case IcsPlayingBlack:
12295             pausing = TRUE;
12296             ModeHighlight();
12297             return;
12298           case PlayFromGameFile:
12299             (void) StopLoadGameTimer();
12300             pausing = TRUE;
12301             ModeHighlight();
12302             break;
12303           case BeginningOfGame:
12304             if (appData.icsActive) return;
12305             /* else fall through */
12306           case MachinePlaysWhite:
12307           case MachinePlaysBlack:
12308           case TwoMachinesPlay:
12309             if (forwardMostMove == 0)
12310               return;           /* don't pause if no one has moved */
12311             if ((gameMode == MachinePlaysWhite &&
12312                  !WhiteOnMove(forwardMostMove)) ||
12313                 (gameMode == MachinePlaysBlack &&
12314                  WhiteOnMove(forwardMostMove))) {
12315                 StopClocks();
12316             }
12317             pausing = TRUE;
12318             ModeHighlight();
12319             break;
12320         }
12321     }
12322 }
12323
12324 void
12325 EditCommentEvent()
12326 {
12327     char title[MSG_SIZ];
12328
12329     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12330       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12331     } else {
12332       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12333                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12334                parseList[currentMove - 1]);
12335     }
12336
12337     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12338 }
12339
12340
12341 void
12342 EditTagsEvent()
12343 {
12344     char *tags = PGNTags(&gameInfo);
12345     bookUp = FALSE;
12346     EditTagsPopUp(tags, NULL);
12347     free(tags);
12348 }
12349
12350 void
12351 AnalyzeModeEvent()
12352 {
12353     if (appData.noChessProgram || gameMode == AnalyzeMode)
12354       return;
12355
12356     if (gameMode != AnalyzeFile) {
12357         if (!appData.icsEngineAnalyze) {
12358                EditGameEvent();
12359                if (gameMode != EditGame) return;
12360         }
12361         ResurrectChessProgram();
12362         SendToProgram("analyze\n", &first);
12363         first.analyzing = TRUE;
12364         /*first.maybeThinking = TRUE;*/
12365         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12366         EngineOutputPopUp();
12367     }
12368     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12369     pausing = FALSE;
12370     ModeHighlight();
12371     SetGameInfo();
12372
12373     StartAnalysisClock();
12374     GetTimeMark(&lastNodeCountTime);
12375     lastNodeCount = 0;
12376 }
12377
12378 void
12379 AnalyzeFileEvent()
12380 {
12381     if (appData.noChessProgram || gameMode == AnalyzeFile)
12382       return;
12383
12384     if (gameMode != AnalyzeMode) {
12385         EditGameEvent();
12386         if (gameMode != EditGame) return;
12387         ResurrectChessProgram();
12388         SendToProgram("analyze\n", &first);
12389         first.analyzing = TRUE;
12390         /*first.maybeThinking = TRUE;*/
12391         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12392         EngineOutputPopUp();
12393     }
12394     gameMode = AnalyzeFile;
12395     pausing = FALSE;
12396     ModeHighlight();
12397     SetGameInfo();
12398
12399     StartAnalysisClock();
12400     GetTimeMark(&lastNodeCountTime);
12401     lastNodeCount = 0;
12402 }
12403
12404 void
12405 MachineWhiteEvent()
12406 {
12407     char buf[MSG_SIZ];
12408     char *bookHit = NULL;
12409
12410     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12411       return;
12412
12413
12414     if (gameMode == PlayFromGameFile ||
12415         gameMode == TwoMachinesPlay  ||
12416         gameMode == Training         ||
12417         gameMode == AnalyzeMode      ||
12418         gameMode == EndOfGame)
12419         EditGameEvent();
12420
12421     if (gameMode == EditPosition)
12422         EditPositionDone(TRUE);
12423
12424     if (!WhiteOnMove(currentMove)) {
12425         DisplayError(_("It is not White's turn"), 0);
12426         return;
12427     }
12428
12429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12430       ExitAnalyzeMode();
12431
12432     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12433         gameMode == AnalyzeFile)
12434         TruncateGame();
12435
12436     ResurrectChessProgram();    /* in case it isn't running */
12437     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12438         gameMode = MachinePlaysWhite;
12439         ResetClocks();
12440     } else
12441     gameMode = MachinePlaysWhite;
12442     pausing = FALSE;
12443     ModeHighlight();
12444     SetGameInfo();
12445     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12446     DisplayTitle(buf);
12447     if (first.sendName) {
12448       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12449       SendToProgram(buf, &first);
12450     }
12451     if (first.sendTime) {
12452       if (first.useColors) {
12453         SendToProgram("black\n", &first); /*gnu kludge*/
12454       }
12455       SendTimeRemaining(&first, TRUE);
12456     }
12457     if (first.useColors) {
12458       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12459     }
12460     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12461     SetMachineThinkingEnables();
12462     first.maybeThinking = TRUE;
12463     StartClocks();
12464     firstMove = FALSE;
12465
12466     if (appData.autoFlipView && !flipView) {
12467       flipView = !flipView;
12468       DrawPosition(FALSE, NULL);
12469       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12470     }
12471
12472     if(bookHit) { // [HGM] book: simulate book reply
12473         static char bookMove[MSG_SIZ]; // a bit generous?
12474
12475         programStats.nodes = programStats.depth = programStats.time =
12476         programStats.score = programStats.got_only_move = 0;
12477         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12478
12479         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12480         strcat(bookMove, bookHit);
12481         HandleMachineMove(bookMove, &first);
12482     }
12483 }
12484
12485 void
12486 MachineBlackEvent()
12487 {
12488   char buf[MSG_SIZ];
12489   char *bookHit = NULL;
12490
12491     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12492         return;
12493
12494
12495     if (gameMode == PlayFromGameFile ||
12496         gameMode == TwoMachinesPlay  ||
12497         gameMode == Training         ||
12498         gameMode == AnalyzeMode      ||
12499         gameMode == EndOfGame)
12500         EditGameEvent();
12501
12502     if (gameMode == EditPosition)
12503         EditPositionDone(TRUE);
12504
12505     if (WhiteOnMove(currentMove)) {
12506         DisplayError(_("It is not Black's turn"), 0);
12507         return;
12508     }
12509
12510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12511       ExitAnalyzeMode();
12512
12513     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12514         gameMode == AnalyzeFile)
12515         TruncateGame();
12516
12517     ResurrectChessProgram();    /* in case it isn't running */
12518     gameMode = MachinePlaysBlack;
12519     pausing = FALSE;
12520     ModeHighlight();
12521     SetGameInfo();
12522     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12523     DisplayTitle(buf);
12524     if (first.sendName) {
12525       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12526       SendToProgram(buf, &first);
12527     }
12528     if (first.sendTime) {
12529       if (first.useColors) {
12530         SendToProgram("white\n", &first); /*gnu kludge*/
12531       }
12532       SendTimeRemaining(&first, FALSE);
12533     }
12534     if (first.useColors) {
12535       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12536     }
12537     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12538     SetMachineThinkingEnables();
12539     first.maybeThinking = TRUE;
12540     StartClocks();
12541
12542     if (appData.autoFlipView && flipView) {
12543       flipView = !flipView;
12544       DrawPosition(FALSE, NULL);
12545       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12546     }
12547     if(bookHit) { // [HGM] book: simulate book reply
12548         static char bookMove[MSG_SIZ]; // a bit generous?
12549
12550         programStats.nodes = programStats.depth = programStats.time =
12551         programStats.score = programStats.got_only_move = 0;
12552         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12553
12554         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12555         strcat(bookMove, bookHit);
12556         HandleMachineMove(bookMove, &first);
12557     }
12558 }
12559
12560
12561 void
12562 DisplayTwoMachinesTitle()
12563 {
12564     char buf[MSG_SIZ];
12565     if (appData.matchGames > 0) {
12566         if (first.twoMachinesColor[0] == 'w') {
12567           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12568                    gameInfo.white, gameInfo.black,
12569                    first.matchWins, second.matchWins,
12570                    matchGame - 1 - (first.matchWins + second.matchWins));
12571         } else {
12572           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12573                    gameInfo.white, gameInfo.black,
12574                    second.matchWins, first.matchWins,
12575                    matchGame - 1 - (first.matchWins + second.matchWins));
12576         }
12577     } else {
12578       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12579     }
12580     DisplayTitle(buf);
12581 }
12582
12583 void
12584 SettingsMenuIfReady()
12585 {
12586   if (second.lastPing != second.lastPong) {
12587     DisplayMessage("", _("Waiting for second chess program"));
12588     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12589     return;
12590   }
12591   ThawUI();
12592   DisplayMessage("", "");
12593   SettingsPopUp(&second);
12594 }
12595
12596 int
12597 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12598 {
12599     char buf[MSG_SIZ];
12600     if (cps->pr == NULL) {
12601         StartChessProgram(cps);
12602         if (cps->protocolVersion == 1) {
12603           retry();
12604         } else {
12605           /* kludge: allow timeout for initial "feature" command */
12606           FreezeUI();
12607           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12608           DisplayMessage("", buf);
12609           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12610         }
12611         return 1;
12612     }
12613     return 0;
12614 }
12615
12616 void
12617 TwoMachinesEvent P((void))
12618 {
12619     int i;
12620     char buf[MSG_SIZ];
12621     ChessProgramState *onmove;
12622     char *bookHit = NULL;
12623     static int stalling = 0;
12624     TimeMark now;
12625     long wait;
12626
12627     if (appData.noChessProgram) return;
12628
12629     switch (gameMode) {
12630       case TwoMachinesPlay:
12631         return;
12632       case MachinePlaysWhite:
12633       case MachinePlaysBlack:
12634         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12635             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12636             return;
12637         }
12638         /* fall through */
12639       case BeginningOfGame:
12640       case PlayFromGameFile:
12641       case EndOfGame:
12642         EditGameEvent();
12643         if (gameMode != EditGame) return;
12644         break;
12645       case EditPosition:
12646         EditPositionDone(TRUE);
12647         break;
12648       case AnalyzeMode:
12649       case AnalyzeFile:
12650         ExitAnalyzeMode();
12651         break;
12652       case EditGame:
12653       default:
12654         break;
12655     }
12656
12657 //    forwardMostMove = currentMove;
12658     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12659
12660     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12661
12662     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12663     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12664       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12665       return;
12666     }
12667     if(!stalling) {
12668       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12669       SendToProgram("force\n", &second);
12670       stalling = 1;
12671       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12672       return;
12673     }
12674     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12675     if(appData.matchPause>10000 || appData.matchPause<10)
12676                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12677     wait = SubtractTimeMarks(&now, &pauseStart);
12678     if(wait < appData.matchPause) {
12679         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12680         return;
12681     }
12682     stalling = 0;
12683     DisplayMessage("", "");
12684     if (startedFromSetupPosition) {
12685         SendBoard(&second, backwardMostMove);
12686     if (appData.debugMode) {
12687         fprintf(debugFP, "Two Machines\n");
12688     }
12689     }
12690     for (i = backwardMostMove; i < forwardMostMove; i++) {
12691         SendMoveToProgram(i, &second);
12692     }
12693
12694     gameMode = TwoMachinesPlay;
12695     pausing = FALSE;
12696     ModeHighlight();
12697     SetGameInfo();
12698     DisplayTwoMachinesTitle();
12699     firstMove = TRUE;
12700     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12701         onmove = &first;
12702     } else {
12703         onmove = &second;
12704     }
12705     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12706     SendToProgram(first.computerString, &first);
12707     if (first.sendName) {
12708       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12709       SendToProgram(buf, &first);
12710     }
12711     SendToProgram(second.computerString, &second);
12712     if (second.sendName) {
12713       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12714       SendToProgram(buf, &second);
12715     }
12716
12717     ResetClocks();
12718     if (!first.sendTime || !second.sendTime) {
12719         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12720         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12721     }
12722     if (onmove->sendTime) {
12723       if (onmove->useColors) {
12724         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12725       }
12726       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12727     }
12728     if (onmove->useColors) {
12729       SendToProgram(onmove->twoMachinesColor, onmove);
12730     }
12731     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12732 //    SendToProgram("go\n", onmove);
12733     onmove->maybeThinking = TRUE;
12734     SetMachineThinkingEnables();
12735
12736     StartClocks();
12737
12738     if(bookHit) { // [HGM] book: simulate book reply
12739         static char bookMove[MSG_SIZ]; // a bit generous?
12740
12741         programStats.nodes = programStats.depth = programStats.time =
12742         programStats.score = programStats.got_only_move = 0;
12743         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12744
12745         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12746         strcat(bookMove, bookHit);
12747         savedMessage = bookMove; // args for deferred call
12748         savedState = onmove;
12749         ScheduleDelayedEvent(DeferredBookMove, 1);
12750     }
12751 }
12752
12753 void
12754 TrainingEvent()
12755 {
12756     if (gameMode == Training) {
12757       SetTrainingModeOff();
12758       gameMode = PlayFromGameFile;
12759       DisplayMessage("", _("Training mode off"));
12760     } else {
12761       gameMode = Training;
12762       animateTraining = appData.animate;
12763
12764       /* make sure we are not already at the end of the game */
12765       if (currentMove < forwardMostMove) {
12766         SetTrainingModeOn();
12767         DisplayMessage("", _("Training mode on"));
12768       } else {
12769         gameMode = PlayFromGameFile;
12770         DisplayError(_("Already at end of game"), 0);
12771       }
12772     }
12773     ModeHighlight();
12774 }
12775
12776 void
12777 IcsClientEvent()
12778 {
12779     if (!appData.icsActive) return;
12780     switch (gameMode) {
12781       case IcsPlayingWhite:
12782       case IcsPlayingBlack:
12783       case IcsObserving:
12784       case IcsIdle:
12785       case BeginningOfGame:
12786       case IcsExamining:
12787         return;
12788
12789       case EditGame:
12790         break;
12791
12792       case EditPosition:
12793         EditPositionDone(TRUE);
12794         break;
12795
12796       case AnalyzeMode:
12797       case AnalyzeFile:
12798         ExitAnalyzeMode();
12799         break;
12800
12801       default:
12802         EditGameEvent();
12803         break;
12804     }
12805
12806     gameMode = IcsIdle;
12807     ModeHighlight();
12808     return;
12809 }
12810
12811
12812 void
12813 EditGameEvent()
12814 {
12815     int i;
12816
12817     switch (gameMode) {
12818       case Training:
12819         SetTrainingModeOff();
12820         break;
12821       case MachinePlaysWhite:
12822       case MachinePlaysBlack:
12823       case BeginningOfGame:
12824         SendToProgram("force\n", &first);
12825         SetUserThinkingEnables();
12826         break;
12827       case PlayFromGameFile:
12828         (void) StopLoadGameTimer();
12829         if (gameFileFP != NULL) {
12830             gameFileFP = NULL;
12831         }
12832         break;
12833       case EditPosition:
12834         EditPositionDone(TRUE);
12835         break;
12836       case AnalyzeMode:
12837       case AnalyzeFile:
12838         ExitAnalyzeMode();
12839         SendToProgram("force\n", &first);
12840         break;
12841       case TwoMachinesPlay:
12842         GameEnds(EndOfFile, NULL, GE_PLAYER);
12843         ResurrectChessProgram();
12844         SetUserThinkingEnables();
12845         break;
12846       case EndOfGame:
12847         ResurrectChessProgram();
12848         break;
12849       case IcsPlayingBlack:
12850       case IcsPlayingWhite:
12851         DisplayError(_("Warning: You are still playing a game"), 0);
12852         break;
12853       case IcsObserving:
12854         DisplayError(_("Warning: You are still observing a game"), 0);
12855         break;
12856       case IcsExamining:
12857         DisplayError(_("Warning: You are still examining a game"), 0);
12858         break;
12859       case IcsIdle:
12860         break;
12861       case EditGame:
12862       default:
12863         return;
12864     }
12865
12866     pausing = FALSE;
12867     StopClocks();
12868     first.offeredDraw = second.offeredDraw = 0;
12869
12870     if (gameMode == PlayFromGameFile) {
12871         whiteTimeRemaining = timeRemaining[0][currentMove];
12872         blackTimeRemaining = timeRemaining[1][currentMove];
12873         DisplayTitle("");
12874     }
12875
12876     if (gameMode == MachinePlaysWhite ||
12877         gameMode == MachinePlaysBlack ||
12878         gameMode == TwoMachinesPlay ||
12879         gameMode == EndOfGame) {
12880         i = forwardMostMove;
12881         while (i > currentMove) {
12882             SendToProgram("undo\n", &first);
12883             i--;
12884         }
12885         whiteTimeRemaining = timeRemaining[0][currentMove];
12886         blackTimeRemaining = timeRemaining[1][currentMove];
12887         DisplayBothClocks();
12888         if (whiteFlag || blackFlag) {
12889             whiteFlag = blackFlag = 0;
12890         }
12891         DisplayTitle("");
12892     }
12893
12894     gameMode = EditGame;
12895     ModeHighlight();
12896     SetGameInfo();
12897 }
12898
12899
12900 void
12901 EditPositionEvent()
12902 {
12903     if (gameMode == EditPosition) {
12904         EditGameEvent();
12905         return;
12906     }
12907
12908     EditGameEvent();
12909     if (gameMode != EditGame) return;
12910
12911     gameMode = EditPosition;
12912     ModeHighlight();
12913     SetGameInfo();
12914     if (currentMove > 0)
12915       CopyBoard(boards[0], boards[currentMove]);
12916
12917     blackPlaysFirst = !WhiteOnMove(currentMove);
12918     ResetClocks();
12919     currentMove = forwardMostMove = backwardMostMove = 0;
12920     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12921     DisplayMove(-1);
12922 }
12923
12924 void
12925 ExitAnalyzeMode()
12926 {
12927     /* [DM] icsEngineAnalyze - possible call from other functions */
12928     if (appData.icsEngineAnalyze) {
12929         appData.icsEngineAnalyze = FALSE;
12930
12931         DisplayMessage("",_("Close ICS engine analyze..."));
12932     }
12933     if (first.analysisSupport && first.analyzing) {
12934       SendToProgram("exit\n", &first);
12935       first.analyzing = FALSE;
12936     }
12937     thinkOutput[0] = NULLCHAR;
12938 }
12939
12940 void
12941 EditPositionDone(Boolean fakeRights)
12942 {
12943     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12944
12945     startedFromSetupPosition = TRUE;
12946     InitChessProgram(&first, FALSE);
12947     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12948       boards[0][EP_STATUS] = EP_NONE;
12949       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12950     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12951         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12952         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12953       } else boards[0][CASTLING][2] = NoRights;
12954     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12955         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12956         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12957       } else boards[0][CASTLING][5] = NoRights;
12958     }
12959     SendToProgram("force\n", &first);
12960     if (blackPlaysFirst) {
12961         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12962         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12963         currentMove = forwardMostMove = backwardMostMove = 1;
12964         CopyBoard(boards[1], boards[0]);
12965     } else {
12966         currentMove = forwardMostMove = backwardMostMove = 0;
12967     }
12968     SendBoard(&first, forwardMostMove);
12969     if (appData.debugMode) {
12970         fprintf(debugFP, "EditPosDone\n");
12971     }
12972     DisplayTitle("");
12973     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12974     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12975     gameMode = EditGame;
12976     ModeHighlight();
12977     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12978     ClearHighlights(); /* [AS] */
12979 }
12980
12981 /* Pause for `ms' milliseconds */
12982 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12983 void
12984 TimeDelay(ms)
12985      long ms;
12986 {
12987     TimeMark m1, m2;
12988
12989     GetTimeMark(&m1);
12990     do {
12991         GetTimeMark(&m2);
12992     } while (SubtractTimeMarks(&m2, &m1) < ms);
12993 }
12994
12995 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12996 void
12997 SendMultiLineToICS(buf)
12998      char *buf;
12999 {
13000     char temp[MSG_SIZ+1], *p;
13001     int len;
13002
13003     len = strlen(buf);
13004     if (len > MSG_SIZ)
13005       len = MSG_SIZ;
13006
13007     strncpy(temp, buf, len);
13008     temp[len] = 0;
13009
13010     p = temp;
13011     while (*p) {
13012         if (*p == '\n' || *p == '\r')
13013           *p = ' ';
13014         ++p;
13015     }
13016
13017     strcat(temp, "\n");
13018     SendToICS(temp);
13019     SendToPlayer(temp, strlen(temp));
13020 }
13021
13022 void
13023 SetWhiteToPlayEvent()
13024 {
13025     if (gameMode == EditPosition) {
13026         blackPlaysFirst = FALSE;
13027         DisplayBothClocks();    /* works because currentMove is 0 */
13028     } else if (gameMode == IcsExamining) {
13029         SendToICS(ics_prefix);
13030         SendToICS("tomove white\n");
13031     }
13032 }
13033
13034 void
13035 SetBlackToPlayEvent()
13036 {
13037     if (gameMode == EditPosition) {
13038         blackPlaysFirst = TRUE;
13039         currentMove = 1;        /* kludge */
13040         DisplayBothClocks();
13041         currentMove = 0;
13042     } else if (gameMode == IcsExamining) {
13043         SendToICS(ics_prefix);
13044         SendToICS("tomove black\n");
13045     }
13046 }
13047
13048 void
13049 EditPositionMenuEvent(selection, x, y)
13050      ChessSquare selection;
13051      int x, y;
13052 {
13053     char buf[MSG_SIZ];
13054     ChessSquare piece = boards[0][y][x];
13055
13056     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13057
13058     switch (selection) {
13059       case ClearBoard:
13060         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13061             SendToICS(ics_prefix);
13062             SendToICS("bsetup clear\n");
13063         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13064             SendToICS(ics_prefix);
13065             SendToICS("clearboard\n");
13066         } else {
13067             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13068                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13069                 for (y = 0; y < BOARD_HEIGHT; y++) {
13070                     if (gameMode == IcsExamining) {
13071                         if (boards[currentMove][y][x] != EmptySquare) {
13072                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13073                                     AAA + x, ONE + y);
13074                             SendToICS(buf);
13075                         }
13076                     } else {
13077                         boards[0][y][x] = p;
13078                     }
13079                 }
13080             }
13081         }
13082         if (gameMode == EditPosition) {
13083             DrawPosition(FALSE, boards[0]);
13084         }
13085         break;
13086
13087       case WhitePlay:
13088         SetWhiteToPlayEvent();
13089         break;
13090
13091       case BlackPlay:
13092         SetBlackToPlayEvent();
13093         break;
13094
13095       case EmptySquare:
13096         if (gameMode == IcsExamining) {
13097             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13098             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13099             SendToICS(buf);
13100         } else {
13101             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13102                 if(x == BOARD_LEFT-2) {
13103                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13104                     boards[0][y][1] = 0;
13105                 } else
13106                 if(x == BOARD_RGHT+1) {
13107                     if(y >= gameInfo.holdingsSize) break;
13108                     boards[0][y][BOARD_WIDTH-2] = 0;
13109                 } else break;
13110             }
13111             boards[0][y][x] = EmptySquare;
13112             DrawPosition(FALSE, boards[0]);
13113         }
13114         break;
13115
13116       case PromotePiece:
13117         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13118            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13119             selection = (ChessSquare) (PROMOTED piece);
13120         } else if(piece == EmptySquare) selection = WhiteSilver;
13121         else selection = (ChessSquare)((int)piece - 1);
13122         goto defaultlabel;
13123
13124       case DemotePiece:
13125         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13126            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13127             selection = (ChessSquare) (DEMOTED piece);
13128         } else if(piece == EmptySquare) selection = BlackSilver;
13129         else selection = (ChessSquare)((int)piece + 1);
13130         goto defaultlabel;
13131
13132       case WhiteQueen:
13133       case BlackQueen:
13134         if(gameInfo.variant == VariantShatranj ||
13135            gameInfo.variant == VariantXiangqi  ||
13136            gameInfo.variant == VariantCourier  ||
13137            gameInfo.variant == VariantMakruk     )
13138             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13139         goto defaultlabel;
13140
13141       case WhiteKing:
13142       case BlackKing:
13143         if(gameInfo.variant == VariantXiangqi)
13144             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13145         if(gameInfo.variant == VariantKnightmate)
13146             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13147       default:
13148         defaultlabel:
13149         if (gameMode == IcsExamining) {
13150             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13151             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13152                      PieceToChar(selection), AAA + x, ONE + y);
13153             SendToICS(buf);
13154         } else {
13155             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13156                 int n;
13157                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13158                     n = PieceToNumber(selection - BlackPawn);
13159                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13160                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13161                     boards[0][BOARD_HEIGHT-1-n][1]++;
13162                 } else
13163                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13164                     n = PieceToNumber(selection);
13165                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13166                     boards[0][n][BOARD_WIDTH-1] = selection;
13167                     boards[0][n][BOARD_WIDTH-2]++;
13168                 }
13169             } else
13170             boards[0][y][x] = selection;
13171             DrawPosition(TRUE, boards[0]);
13172         }
13173         break;
13174     }
13175 }
13176
13177
13178 void
13179 DropMenuEvent(selection, x, y)
13180      ChessSquare selection;
13181      int x, y;
13182 {
13183     ChessMove moveType;
13184
13185     switch (gameMode) {
13186       case IcsPlayingWhite:
13187       case MachinePlaysBlack:
13188         if (!WhiteOnMove(currentMove)) {
13189             DisplayMoveError(_("It is Black's turn"));
13190             return;
13191         }
13192         moveType = WhiteDrop;
13193         break;
13194       case IcsPlayingBlack:
13195       case MachinePlaysWhite:
13196         if (WhiteOnMove(currentMove)) {
13197             DisplayMoveError(_("It is White's turn"));
13198             return;
13199         }
13200         moveType = BlackDrop;
13201         break;
13202       case EditGame:
13203         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13204         break;
13205       default:
13206         return;
13207     }
13208
13209     if (moveType == BlackDrop && selection < BlackPawn) {
13210       selection = (ChessSquare) ((int) selection
13211                                  + (int) BlackPawn - (int) WhitePawn);
13212     }
13213     if (boards[currentMove][y][x] != EmptySquare) {
13214         DisplayMoveError(_("That square is occupied"));
13215         return;
13216     }
13217
13218     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13219 }
13220
13221 void
13222 AcceptEvent()
13223 {
13224     /* Accept a pending offer of any kind from opponent */
13225
13226     if (appData.icsActive) {
13227         SendToICS(ics_prefix);
13228         SendToICS("accept\n");
13229     } else if (cmailMsgLoaded) {
13230         if (currentMove == cmailOldMove &&
13231             commentList[cmailOldMove] != NULL &&
13232             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13233                    "Black offers a draw" : "White offers a draw")) {
13234             TruncateGame();
13235             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13236             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13237         } else {
13238             DisplayError(_("There is no pending offer on this move"), 0);
13239             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13240         }
13241     } else {
13242         /* Not used for offers from chess program */
13243     }
13244 }
13245
13246 void
13247 DeclineEvent()
13248 {
13249     /* Decline a pending offer of any kind from opponent */
13250
13251     if (appData.icsActive) {
13252         SendToICS(ics_prefix);
13253         SendToICS("decline\n");
13254     } else if (cmailMsgLoaded) {
13255         if (currentMove == cmailOldMove &&
13256             commentList[cmailOldMove] != NULL &&
13257             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13258                    "Black offers a draw" : "White offers a draw")) {
13259 #ifdef NOTDEF
13260             AppendComment(cmailOldMove, "Draw declined", TRUE);
13261             DisplayComment(cmailOldMove - 1, "Draw declined");
13262 #endif /*NOTDEF*/
13263         } else {
13264             DisplayError(_("There is no pending offer on this move"), 0);
13265         }
13266     } else {
13267         /* Not used for offers from chess program */
13268     }
13269 }
13270
13271 void
13272 RematchEvent()
13273 {
13274     /* Issue ICS rematch command */
13275     if (appData.icsActive) {
13276         SendToICS(ics_prefix);
13277         SendToICS("rematch\n");
13278     }
13279 }
13280
13281 void
13282 CallFlagEvent()
13283 {
13284     /* Call your opponent's flag (claim a win on time) */
13285     if (appData.icsActive) {
13286         SendToICS(ics_prefix);
13287         SendToICS("flag\n");
13288     } else {
13289         switch (gameMode) {
13290           default:
13291             return;
13292           case MachinePlaysWhite:
13293             if (whiteFlag) {
13294                 if (blackFlag)
13295                   GameEnds(GameIsDrawn, "Both players ran out of time",
13296                            GE_PLAYER);
13297                 else
13298                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13299             } else {
13300                 DisplayError(_("Your opponent is not out of time"), 0);
13301             }
13302             break;
13303           case MachinePlaysBlack:
13304             if (blackFlag) {
13305                 if (whiteFlag)
13306                   GameEnds(GameIsDrawn, "Both players ran out of time",
13307                            GE_PLAYER);
13308                 else
13309                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13310             } else {
13311                 DisplayError(_("Your opponent is not out of time"), 0);
13312             }
13313             break;
13314         }
13315     }
13316 }
13317
13318 void
13319 ClockClick(int which)
13320 {       // [HGM] code moved to back-end from winboard.c
13321         if(which) { // black clock
13322           if (gameMode == EditPosition || gameMode == IcsExamining) {
13323             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13324             SetBlackToPlayEvent();
13325           } else if (gameMode == EditGame || shiftKey) {
13326             AdjustClock(which, -1);
13327           } else if (gameMode == IcsPlayingWhite ||
13328                      gameMode == MachinePlaysBlack) {
13329             CallFlagEvent();
13330           }
13331         } else { // white clock
13332           if (gameMode == EditPosition || gameMode == IcsExamining) {
13333             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13334             SetWhiteToPlayEvent();
13335           } else if (gameMode == EditGame || shiftKey) {
13336             AdjustClock(which, -1);
13337           } else if (gameMode == IcsPlayingBlack ||
13338                    gameMode == MachinePlaysWhite) {
13339             CallFlagEvent();
13340           }
13341         }
13342 }
13343
13344 void
13345 DrawEvent()
13346 {
13347     /* Offer draw or accept pending draw offer from opponent */
13348
13349     if (appData.icsActive) {
13350         /* Note: tournament rules require draw offers to be
13351            made after you make your move but before you punch
13352            your clock.  Currently ICS doesn't let you do that;
13353            instead, you immediately punch your clock after making
13354            a move, but you can offer a draw at any time. */
13355
13356         SendToICS(ics_prefix);
13357         SendToICS("draw\n");
13358         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13359     } else if (cmailMsgLoaded) {
13360         if (currentMove == cmailOldMove &&
13361             commentList[cmailOldMove] != NULL &&
13362             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13363                    "Black offers a draw" : "White offers a draw")) {
13364             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13365             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13366         } else if (currentMove == cmailOldMove + 1) {
13367             char *offer = WhiteOnMove(cmailOldMove) ?
13368               "White offers a draw" : "Black offers a draw";
13369             AppendComment(currentMove, offer, TRUE);
13370             DisplayComment(currentMove - 1, offer);
13371             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13372         } else {
13373             DisplayError(_("You must make your move before offering a draw"), 0);
13374             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13375         }
13376     } else if (first.offeredDraw) {
13377         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13378     } else {
13379         if (first.sendDrawOffers) {
13380             SendToProgram("draw\n", &first);
13381             userOfferedDraw = TRUE;
13382         }
13383     }
13384 }
13385
13386 void
13387 AdjournEvent()
13388 {
13389     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13390
13391     if (appData.icsActive) {
13392         SendToICS(ics_prefix);
13393         SendToICS("adjourn\n");
13394     } else {
13395         /* Currently GNU Chess doesn't offer or accept Adjourns */
13396     }
13397 }
13398
13399
13400 void
13401 AbortEvent()
13402 {
13403     /* Offer Abort or accept pending Abort offer from opponent */
13404
13405     if (appData.icsActive) {
13406         SendToICS(ics_prefix);
13407         SendToICS("abort\n");
13408     } else {
13409         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13410     }
13411 }
13412
13413 void
13414 ResignEvent()
13415 {
13416     /* Resign.  You can do this even if it's not your turn. */
13417
13418     if (appData.icsActive) {
13419         SendToICS(ics_prefix);
13420         SendToICS("resign\n");
13421     } else {
13422         switch (gameMode) {
13423           case MachinePlaysWhite:
13424             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13425             break;
13426           case MachinePlaysBlack:
13427             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13428             break;
13429           case EditGame:
13430             if (cmailMsgLoaded) {
13431                 TruncateGame();
13432                 if (WhiteOnMove(cmailOldMove)) {
13433                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13434                 } else {
13435                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13436                 }
13437                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13438             }
13439             break;
13440           default:
13441             break;
13442         }
13443     }
13444 }
13445
13446
13447 void
13448 StopObservingEvent()
13449 {
13450     /* Stop observing current games */
13451     SendToICS(ics_prefix);
13452     SendToICS("unobserve\n");
13453 }
13454
13455 void
13456 StopExaminingEvent()
13457 {
13458     /* Stop observing current game */
13459     SendToICS(ics_prefix);
13460     SendToICS("unexamine\n");
13461 }
13462
13463 void
13464 ForwardInner(target)
13465      int target;
13466 {
13467     int limit;
13468
13469     if (appData.debugMode)
13470         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13471                 target, currentMove, forwardMostMove);
13472
13473     if (gameMode == EditPosition)
13474       return;
13475
13476     if (gameMode == PlayFromGameFile && !pausing)
13477       PauseEvent();
13478
13479     if (gameMode == IcsExamining && pausing)
13480       limit = pauseExamForwardMostMove;
13481     else
13482       limit = forwardMostMove;
13483
13484     if (target > limit) target = limit;
13485
13486     if (target > 0 && moveList[target - 1][0]) {
13487         int fromX, fromY, toX, toY;
13488         toX = moveList[target - 1][2] - AAA;
13489         toY = moveList[target - 1][3] - ONE;
13490         if (moveList[target - 1][1] == '@') {
13491             if (appData.highlightLastMove) {
13492                 SetHighlights(-1, -1, toX, toY);
13493             }
13494         } else {
13495             fromX = moveList[target - 1][0] - AAA;
13496             fromY = moveList[target - 1][1] - ONE;
13497             if (target == currentMove + 1) {
13498                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13499             }
13500             if (appData.highlightLastMove) {
13501                 SetHighlights(fromX, fromY, toX, toY);
13502             }
13503         }
13504     }
13505     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13506         gameMode == Training || gameMode == PlayFromGameFile ||
13507         gameMode == AnalyzeFile) {
13508         while (currentMove < target) {
13509             SendMoveToProgram(currentMove++, &first);
13510         }
13511     } else {
13512         currentMove = target;
13513     }
13514
13515     if (gameMode == EditGame || gameMode == EndOfGame) {
13516         whiteTimeRemaining = timeRemaining[0][currentMove];
13517         blackTimeRemaining = timeRemaining[1][currentMove];
13518     }
13519     DisplayBothClocks();
13520     DisplayMove(currentMove - 1);
13521     DrawPosition(FALSE, boards[currentMove]);
13522     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13523     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13524         DisplayComment(currentMove - 1, commentList[currentMove]);
13525     }
13526     DisplayBook(currentMove);
13527 }
13528
13529
13530 void
13531 ForwardEvent()
13532 {
13533     if (gameMode == IcsExamining && !pausing) {
13534         SendToICS(ics_prefix);
13535         SendToICS("forward\n");
13536     } else {
13537         ForwardInner(currentMove + 1);
13538     }
13539 }
13540
13541 void
13542 ToEndEvent()
13543 {
13544     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13545         /* to optimze, we temporarily turn off analysis mode while we feed
13546          * the remaining moves to the engine. Otherwise we get analysis output
13547          * after each move.
13548          */
13549         if (first.analysisSupport) {
13550           SendToProgram("exit\nforce\n", &first);
13551           first.analyzing = FALSE;
13552         }
13553     }
13554
13555     if (gameMode == IcsExamining && !pausing) {
13556         SendToICS(ics_prefix);
13557         SendToICS("forward 999999\n");
13558     } else {
13559         ForwardInner(forwardMostMove);
13560     }
13561
13562     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13563         /* we have fed all the moves, so reactivate analysis mode */
13564         SendToProgram("analyze\n", &first);
13565         first.analyzing = TRUE;
13566         /*first.maybeThinking = TRUE;*/
13567         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13568     }
13569 }
13570
13571 void
13572 BackwardInner(target)
13573      int target;
13574 {
13575     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13576
13577     if (appData.debugMode)
13578         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13579                 target, currentMove, forwardMostMove);
13580
13581     if (gameMode == EditPosition) return;
13582     if (currentMove <= backwardMostMove) {
13583         ClearHighlights();
13584         DrawPosition(full_redraw, boards[currentMove]);
13585         return;
13586     }
13587     if (gameMode == PlayFromGameFile && !pausing)
13588       PauseEvent();
13589
13590     if (moveList[target][0]) {
13591         int fromX, fromY, toX, toY;
13592         toX = moveList[target][2] - AAA;
13593         toY = moveList[target][3] - ONE;
13594         if (moveList[target][1] == '@') {
13595             if (appData.highlightLastMove) {
13596                 SetHighlights(-1, -1, toX, toY);
13597             }
13598         } else {
13599             fromX = moveList[target][0] - AAA;
13600             fromY = moveList[target][1] - ONE;
13601             if (target == currentMove - 1) {
13602                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13603             }
13604             if (appData.highlightLastMove) {
13605                 SetHighlights(fromX, fromY, toX, toY);
13606             }
13607         }
13608     }
13609     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13610         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13611         while (currentMove > target) {
13612             SendToProgram("undo\n", &first);
13613             currentMove--;
13614         }
13615     } else {
13616         currentMove = target;
13617     }
13618
13619     if (gameMode == EditGame || gameMode == EndOfGame) {
13620         whiteTimeRemaining = timeRemaining[0][currentMove];
13621         blackTimeRemaining = timeRemaining[1][currentMove];
13622     }
13623     DisplayBothClocks();
13624     DisplayMove(currentMove - 1);
13625     DrawPosition(full_redraw, boards[currentMove]);
13626     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13627     // [HGM] PV info: routine tests if comment empty
13628     DisplayComment(currentMove - 1, commentList[currentMove]);
13629     DisplayBook(currentMove);
13630 }
13631
13632 void
13633 BackwardEvent()
13634 {
13635     if (gameMode == IcsExamining && !pausing) {
13636         SendToICS(ics_prefix);
13637         SendToICS("backward\n");
13638     } else {
13639         BackwardInner(currentMove - 1);
13640     }
13641 }
13642
13643 void
13644 ToStartEvent()
13645 {
13646     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13647         /* to optimize, we temporarily turn off analysis mode while we undo
13648          * all the moves. Otherwise we get analysis output after each undo.
13649          */
13650         if (first.analysisSupport) {
13651           SendToProgram("exit\nforce\n", &first);
13652           first.analyzing = FALSE;
13653         }
13654     }
13655
13656     if (gameMode == IcsExamining && !pausing) {
13657         SendToICS(ics_prefix);
13658         SendToICS("backward 999999\n");
13659     } else {
13660         BackwardInner(backwardMostMove);
13661     }
13662
13663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13664         /* we have fed all the moves, so reactivate analysis mode */
13665         SendToProgram("analyze\n", &first);
13666         first.analyzing = TRUE;
13667         /*first.maybeThinking = TRUE;*/
13668         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13669     }
13670 }
13671
13672 void
13673 ToNrEvent(int to)
13674 {
13675   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13676   if (to >= forwardMostMove) to = forwardMostMove;
13677   if (to <= backwardMostMove) to = backwardMostMove;
13678   if (to < currentMove) {
13679     BackwardInner(to);
13680   } else {
13681     ForwardInner(to);
13682   }
13683 }
13684
13685 void
13686 RevertEvent(Boolean annotate)
13687 {
13688     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13689         return;
13690     }
13691     if (gameMode != IcsExamining) {
13692         DisplayError(_("You are not examining a game"), 0);
13693         return;
13694     }
13695     if (pausing) {
13696         DisplayError(_("You can't revert while pausing"), 0);
13697         return;
13698     }
13699     SendToICS(ics_prefix);
13700     SendToICS("revert\n");
13701 }
13702
13703 void
13704 RetractMoveEvent()
13705 {
13706     switch (gameMode) {
13707       case MachinePlaysWhite:
13708       case MachinePlaysBlack:
13709         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13710             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13711             return;
13712         }
13713         if (forwardMostMove < 2) return;
13714         currentMove = forwardMostMove = forwardMostMove - 2;
13715         whiteTimeRemaining = timeRemaining[0][currentMove];
13716         blackTimeRemaining = timeRemaining[1][currentMove];
13717         DisplayBothClocks();
13718         DisplayMove(currentMove - 1);
13719         ClearHighlights();/*!! could figure this out*/
13720         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13721         SendToProgram("remove\n", &first);
13722         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13723         break;
13724
13725       case BeginningOfGame:
13726       default:
13727         break;
13728
13729       case IcsPlayingWhite:
13730       case IcsPlayingBlack:
13731         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13732             SendToICS(ics_prefix);
13733             SendToICS("takeback 2\n");
13734         } else {
13735             SendToICS(ics_prefix);
13736             SendToICS("takeback 1\n");
13737         }
13738         break;
13739     }
13740 }
13741
13742 void
13743 MoveNowEvent()
13744 {
13745     ChessProgramState *cps;
13746
13747     switch (gameMode) {
13748       case MachinePlaysWhite:
13749         if (!WhiteOnMove(forwardMostMove)) {
13750             DisplayError(_("It is your turn"), 0);
13751             return;
13752         }
13753         cps = &first;
13754         break;
13755       case MachinePlaysBlack:
13756         if (WhiteOnMove(forwardMostMove)) {
13757             DisplayError(_("It is your turn"), 0);
13758             return;
13759         }
13760         cps = &first;
13761         break;
13762       case TwoMachinesPlay:
13763         if (WhiteOnMove(forwardMostMove) ==
13764             (first.twoMachinesColor[0] == 'w')) {
13765             cps = &first;
13766         } else {
13767             cps = &second;
13768         }
13769         break;
13770       case BeginningOfGame:
13771       default:
13772         return;
13773     }
13774     SendToProgram("?\n", cps);
13775 }
13776
13777 void
13778 TruncateGameEvent()
13779 {
13780     EditGameEvent();
13781     if (gameMode != EditGame) return;
13782     TruncateGame();
13783 }
13784
13785 void
13786 TruncateGame()
13787 {
13788     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13789     if (forwardMostMove > currentMove) {
13790         if (gameInfo.resultDetails != NULL) {
13791             free(gameInfo.resultDetails);
13792             gameInfo.resultDetails = NULL;
13793             gameInfo.result = GameUnfinished;
13794         }
13795         forwardMostMove = currentMove;
13796         HistorySet(parseList, backwardMostMove, forwardMostMove,
13797                    currentMove-1);
13798     }
13799 }
13800
13801 void
13802 HintEvent()
13803 {
13804     if (appData.noChessProgram) return;
13805     switch (gameMode) {
13806       case MachinePlaysWhite:
13807         if (WhiteOnMove(forwardMostMove)) {
13808             DisplayError(_("Wait until your turn"), 0);
13809             return;
13810         }
13811         break;
13812       case BeginningOfGame:
13813       case MachinePlaysBlack:
13814         if (!WhiteOnMove(forwardMostMove)) {
13815             DisplayError(_("Wait until your turn"), 0);
13816             return;
13817         }
13818         break;
13819       default:
13820         DisplayError(_("No hint available"), 0);
13821         return;
13822     }
13823     SendToProgram("hint\n", &first);
13824     hintRequested = TRUE;
13825 }
13826
13827 void
13828 BookEvent()
13829 {
13830     if (appData.noChessProgram) return;
13831     switch (gameMode) {
13832       case MachinePlaysWhite:
13833         if (WhiteOnMove(forwardMostMove)) {
13834             DisplayError(_("Wait until your turn"), 0);
13835             return;
13836         }
13837         break;
13838       case BeginningOfGame:
13839       case MachinePlaysBlack:
13840         if (!WhiteOnMove(forwardMostMove)) {
13841             DisplayError(_("Wait until your turn"), 0);
13842             return;
13843         }
13844         break;
13845       case EditPosition:
13846         EditPositionDone(TRUE);
13847         break;
13848       case TwoMachinesPlay:
13849         return;
13850       default:
13851         break;
13852     }
13853     SendToProgram("bk\n", &first);
13854     bookOutput[0] = NULLCHAR;
13855     bookRequested = TRUE;
13856 }
13857
13858 void
13859 AboutGameEvent()
13860 {
13861     char *tags = PGNTags(&gameInfo);
13862     TagsPopUp(tags, CmailMsg());
13863     free(tags);
13864 }
13865
13866 /* end button procedures */
13867
13868 void
13869 PrintPosition(fp, move)
13870      FILE *fp;
13871      int move;
13872 {
13873     int i, j;
13874
13875     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13876         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13877             char c = PieceToChar(boards[move][i][j]);
13878             fputc(c == 'x' ? '.' : c, fp);
13879             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13880         }
13881     }
13882     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13883       fprintf(fp, "white to play\n");
13884     else
13885       fprintf(fp, "black to play\n");
13886 }
13887
13888 void
13889 PrintOpponents(fp)
13890      FILE *fp;
13891 {
13892     if (gameInfo.white != NULL) {
13893         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13894     } else {
13895         fprintf(fp, "\n");
13896     }
13897 }
13898
13899 /* Find last component of program's own name, using some heuristics */
13900 void
13901 TidyProgramName(prog, host, buf)
13902      char *prog, *host, buf[MSG_SIZ];
13903 {
13904     char *p, *q;
13905     int local = (strcmp(host, "localhost") == 0);
13906     while (!local && (p = strchr(prog, ';')) != NULL) {
13907         p++;
13908         while (*p == ' ') p++;
13909         prog = p;
13910     }
13911     if (*prog == '"' || *prog == '\'') {
13912         q = strchr(prog + 1, *prog);
13913     } else {
13914         q = strchr(prog, ' ');
13915     }
13916     if (q == NULL) q = prog + strlen(prog);
13917     p = q;
13918     while (p >= prog && *p != '/' && *p != '\\') p--;
13919     p++;
13920     if(p == prog && *p == '"') p++;
13921     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13922     memcpy(buf, p, q - p);
13923     buf[q - p] = NULLCHAR;
13924     if (!local) {
13925         strcat(buf, "@");
13926         strcat(buf, host);
13927     }
13928 }
13929
13930 char *
13931 TimeControlTagValue()
13932 {
13933     char buf[MSG_SIZ];
13934     if (!appData.clockMode) {
13935       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13936     } else if (movesPerSession > 0) {
13937       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13938     } else if (timeIncrement == 0) {
13939       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13940     } else {
13941       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13942     }
13943     return StrSave(buf);
13944 }
13945
13946 void
13947 SetGameInfo()
13948 {
13949     /* This routine is used only for certain modes */
13950     VariantClass v = gameInfo.variant;
13951     ChessMove r = GameUnfinished;
13952     char *p = NULL;
13953
13954     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13955         r = gameInfo.result;
13956         p = gameInfo.resultDetails;
13957         gameInfo.resultDetails = NULL;
13958     }
13959     ClearGameInfo(&gameInfo);
13960     gameInfo.variant = v;
13961
13962     switch (gameMode) {
13963       case MachinePlaysWhite:
13964         gameInfo.event = StrSave( appData.pgnEventHeader );
13965         gameInfo.site = StrSave(HostName());
13966         gameInfo.date = PGNDate();
13967         gameInfo.round = StrSave("-");
13968         gameInfo.white = StrSave(first.tidy);
13969         gameInfo.black = StrSave(UserName());
13970         gameInfo.timeControl = TimeControlTagValue();
13971         break;
13972
13973       case MachinePlaysBlack:
13974         gameInfo.event = StrSave( appData.pgnEventHeader );
13975         gameInfo.site = StrSave(HostName());
13976         gameInfo.date = PGNDate();
13977         gameInfo.round = StrSave("-");
13978         gameInfo.white = StrSave(UserName());
13979         gameInfo.black = StrSave(first.tidy);
13980         gameInfo.timeControl = TimeControlTagValue();
13981         break;
13982
13983       case TwoMachinesPlay:
13984         gameInfo.event = StrSave( appData.pgnEventHeader );
13985         gameInfo.site = StrSave(HostName());
13986         gameInfo.date = PGNDate();
13987         if (roundNr > 0) {
13988             char buf[MSG_SIZ];
13989             snprintf(buf, MSG_SIZ, "%d", roundNr);
13990             gameInfo.round = StrSave(buf);
13991         } else {
13992             gameInfo.round = StrSave("-");
13993         }
13994         if (first.twoMachinesColor[0] == 'w') {
13995             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13996             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13997         } else {
13998             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13999             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14000         }
14001         gameInfo.timeControl = TimeControlTagValue();
14002         break;
14003
14004       case EditGame:
14005         gameInfo.event = StrSave("Edited game");
14006         gameInfo.site = StrSave(HostName());
14007         gameInfo.date = PGNDate();
14008         gameInfo.round = StrSave("-");
14009         gameInfo.white = StrSave("-");
14010         gameInfo.black = StrSave("-");
14011         gameInfo.result = r;
14012         gameInfo.resultDetails = p;
14013         break;
14014
14015       case EditPosition:
14016         gameInfo.event = StrSave("Edited position");
14017         gameInfo.site = StrSave(HostName());
14018         gameInfo.date = PGNDate();
14019         gameInfo.round = StrSave("-");
14020         gameInfo.white = StrSave("-");
14021         gameInfo.black = StrSave("-");
14022         break;
14023
14024       case IcsPlayingWhite:
14025       case IcsPlayingBlack:
14026       case IcsObserving:
14027       case IcsExamining:
14028         break;
14029
14030       case PlayFromGameFile:
14031         gameInfo.event = StrSave("Game from non-PGN file");
14032         gameInfo.site = StrSave(HostName());
14033         gameInfo.date = PGNDate();
14034         gameInfo.round = StrSave("-");
14035         gameInfo.white = StrSave("?");
14036         gameInfo.black = StrSave("?");
14037         break;
14038
14039       default:
14040         break;
14041     }
14042 }
14043
14044 void
14045 ReplaceComment(index, text)
14046      int index;
14047      char *text;
14048 {
14049     int len;
14050     char *p;
14051     float score;
14052
14053     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14054        pvInfoList[index-1].depth == len &&
14055        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14056        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14057     while (*text == '\n') text++;
14058     len = strlen(text);
14059     while (len > 0 && text[len - 1] == '\n') len--;
14060
14061     if (commentList[index] != NULL)
14062       free(commentList[index]);
14063
14064     if (len == 0) {
14065         commentList[index] = NULL;
14066         return;
14067     }
14068   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14069       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14070       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14071     commentList[index] = (char *) malloc(len + 2);
14072     strncpy(commentList[index], text, len);
14073     commentList[index][len] = '\n';
14074     commentList[index][len + 1] = NULLCHAR;
14075   } else {
14076     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14077     char *p;
14078     commentList[index] = (char *) malloc(len + 7);
14079     safeStrCpy(commentList[index], "{\n", 3);
14080     safeStrCpy(commentList[index]+2, text, len+1);
14081     commentList[index][len+2] = NULLCHAR;
14082     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14083     strcat(commentList[index], "\n}\n");
14084   }
14085 }
14086
14087 void
14088 CrushCRs(text)
14089      char *text;
14090 {
14091   char *p = text;
14092   char *q = text;
14093   char ch;
14094
14095   do {
14096     ch = *p++;
14097     if (ch == '\r') continue;
14098     *q++ = ch;
14099   } while (ch != '\0');
14100 }
14101
14102 void
14103 AppendComment(index, text, addBraces)
14104      int index;
14105      char *text;
14106      Boolean addBraces; // [HGM] braces: tells if we should add {}
14107 {
14108     int oldlen, len;
14109     char *old;
14110
14111 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14112     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14113
14114     CrushCRs(text);
14115     while (*text == '\n') text++;
14116     len = strlen(text);
14117     while (len > 0 && text[len - 1] == '\n') len--;
14118
14119     if (len == 0) return;
14120
14121     if (commentList[index] != NULL) {
14122         old = commentList[index];
14123         oldlen = strlen(old);
14124         while(commentList[index][oldlen-1] ==  '\n')
14125           commentList[index][--oldlen] = NULLCHAR;
14126         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14127         safeStrCpy(commentList[index], old, oldlen + len + 6);
14128         free(old);
14129         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14130         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14131           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14132           while (*text == '\n') { text++; len--; }
14133           commentList[index][--oldlen] = NULLCHAR;
14134       }
14135         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14136         else          strcat(commentList[index], "\n");
14137         strcat(commentList[index], text);
14138         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14139         else          strcat(commentList[index], "\n");
14140     } else {
14141         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14142         if(addBraces)
14143           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14144         else commentList[index][0] = NULLCHAR;
14145         strcat(commentList[index], text);
14146         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14147         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14148     }
14149 }
14150
14151 static char * FindStr( char * text, char * sub_text )
14152 {
14153     char * result = strstr( text, sub_text );
14154
14155     if( result != NULL ) {
14156         result += strlen( sub_text );
14157     }
14158
14159     return result;
14160 }
14161
14162 /* [AS] Try to extract PV info from PGN comment */
14163 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14164 char *GetInfoFromComment( int index, char * text )
14165 {
14166     char * sep = text, *p;
14167
14168     if( text != NULL && index > 0 ) {
14169         int score = 0;
14170         int depth = 0;
14171         int time = -1, sec = 0, deci;
14172         char * s_eval = FindStr( text, "[%eval " );
14173         char * s_emt = FindStr( text, "[%emt " );
14174
14175         if( s_eval != NULL || s_emt != NULL ) {
14176             /* New style */
14177             char delim;
14178
14179             if( s_eval != NULL ) {
14180                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14181                     return text;
14182                 }
14183
14184                 if( delim != ']' ) {
14185                     return text;
14186                 }
14187             }
14188
14189             if( s_emt != NULL ) {
14190             }
14191                 return text;
14192         }
14193         else {
14194             /* We expect something like: [+|-]nnn.nn/dd */
14195             int score_lo = 0;
14196
14197             if(*text != '{') return text; // [HGM] braces: must be normal comment
14198
14199             sep = strchr( text, '/' );
14200             if( sep == NULL || sep < (text+4) ) {
14201                 return text;
14202             }
14203
14204             p = text;
14205             if(p[1] == '(') { // comment starts with PV
14206                p = strchr(p, ')'); // locate end of PV
14207                if(p == NULL || sep < p+5) return text;
14208                // at this point we have something like "{(.*) +0.23/6 ..."
14209                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14210                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14211                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14212             }
14213             time = -1; sec = -1; deci = -1;
14214             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14215                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14216                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14217                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14218                 return text;
14219             }
14220
14221             if( score_lo < 0 || score_lo >= 100 ) {
14222                 return text;
14223             }
14224
14225             if(sec >= 0) time = 600*time + 10*sec; else
14226             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14227
14228             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14229
14230             /* [HGM] PV time: now locate end of PV info */
14231             while( *++sep >= '0' && *sep <= '9'); // strip depth
14232             if(time >= 0)
14233             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14234             if(sec >= 0)
14235             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14236             if(deci >= 0)
14237             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14238             while(*sep == ' ') sep++;
14239         }
14240
14241         if( depth <= 0 ) {
14242             return text;
14243         }
14244
14245         if( time < 0 ) {
14246             time = -1;
14247         }
14248
14249         pvInfoList[index-1].depth = depth;
14250         pvInfoList[index-1].score = score;
14251         pvInfoList[index-1].time  = 10*time; // centi-sec
14252         if(*sep == '}') *sep = 0; else *--sep = '{';
14253         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14254     }
14255     return sep;
14256 }
14257
14258 void
14259 SendToProgram(message, cps)
14260      char *message;
14261      ChessProgramState *cps;
14262 {
14263     int count, outCount, error;
14264     char buf[MSG_SIZ];
14265
14266     if (cps->pr == NULL) return;
14267     Attention(cps);
14268
14269     if (appData.debugMode) {
14270         TimeMark now;
14271         GetTimeMark(&now);
14272         fprintf(debugFP, "%ld >%-6s: %s",
14273                 SubtractTimeMarks(&now, &programStartTime),
14274                 cps->which, message);
14275     }
14276
14277     count = strlen(message);
14278     outCount = OutputToProcess(cps->pr, message, count, &error);
14279     if (outCount < count && !exiting
14280                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14281       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14282       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14283         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14284             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14285                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14286                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14287                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14288             } else {
14289                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14290                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14291                 gameInfo.result = res;
14292             }
14293             gameInfo.resultDetails = StrSave(buf);
14294         }
14295         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14296         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14297     }
14298 }
14299
14300 void
14301 ReceiveFromProgram(isr, closure, message, count, error)
14302      InputSourceRef isr;
14303      VOIDSTAR closure;
14304      char *message;
14305      int count;
14306      int error;
14307 {
14308     char *end_str;
14309     char buf[MSG_SIZ];
14310     ChessProgramState *cps = (ChessProgramState *)closure;
14311
14312     if (isr != cps->isr) return; /* Killed intentionally */
14313     if (count <= 0) {
14314         if (count == 0) {
14315             RemoveInputSource(cps->isr);
14316             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14317                     _(cps->which), cps->program);
14318         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14319                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14320                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14321                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14322                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14323                 } else {
14324                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14325                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14326                     gameInfo.result = res;
14327                 }
14328                 gameInfo.resultDetails = StrSave(buf);
14329             }
14330             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14331             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14332         } else {
14333             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14334                     _(cps->which), cps->program);
14335             RemoveInputSource(cps->isr);
14336
14337             /* [AS] Program is misbehaving badly... kill it */
14338             if( count == -2 ) {
14339                 DestroyChildProcess( cps->pr, 9 );
14340                 cps->pr = NoProc;
14341             }
14342
14343             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14344         }
14345         return;
14346     }
14347
14348     if ((end_str = strchr(message, '\r')) != NULL)
14349       *end_str = NULLCHAR;
14350     if ((end_str = strchr(message, '\n')) != NULL)
14351       *end_str = NULLCHAR;
14352
14353     if (appData.debugMode) {
14354         TimeMark now; int print = 1;
14355         char *quote = ""; char c; int i;
14356
14357         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14358                 char start = message[0];
14359                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14360                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14361                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14362                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14363                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14364                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14365                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14366                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14367                    sscanf(message, "hint: %c", &c)!=1 && 
14368                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14369                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14370                     print = (appData.engineComments >= 2);
14371                 }
14372                 message[0] = start; // restore original message
14373         }
14374         if(print) {
14375                 GetTimeMark(&now);
14376                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14377                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14378                         quote,
14379                         message);
14380         }
14381     }
14382
14383     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14384     if (appData.icsEngineAnalyze) {
14385         if (strstr(message, "whisper") != NULL ||
14386              strstr(message, "kibitz") != NULL ||
14387             strstr(message, "tellics") != NULL) return;
14388     }
14389
14390     HandleMachineMove(message, cps);
14391 }
14392
14393
14394 void
14395 SendTimeControl(cps, mps, tc, inc, sd, st)
14396      ChessProgramState *cps;
14397      int mps, inc, sd, st;
14398      long tc;
14399 {
14400     char buf[MSG_SIZ];
14401     int seconds;
14402
14403     if( timeControl_2 > 0 ) {
14404         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14405             tc = timeControl_2;
14406         }
14407     }
14408     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14409     inc /= cps->timeOdds;
14410     st  /= cps->timeOdds;
14411
14412     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14413
14414     if (st > 0) {
14415       /* Set exact time per move, normally using st command */
14416       if (cps->stKludge) {
14417         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14418         seconds = st % 60;
14419         if (seconds == 0) {
14420           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14421         } else {
14422           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14423         }
14424       } else {
14425         snprintf(buf, MSG_SIZ, "st %d\n", st);
14426       }
14427     } else {
14428       /* Set conventional or incremental time control, using level command */
14429       if (seconds == 0) {
14430         /* Note old gnuchess bug -- minutes:seconds used to not work.
14431            Fixed in later versions, but still avoid :seconds
14432            when seconds is 0. */
14433         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14434       } else {
14435         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14436                  seconds, inc/1000.);
14437       }
14438     }
14439     SendToProgram(buf, cps);
14440
14441     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14442     /* Orthogonally, limit search to given depth */
14443     if (sd > 0) {
14444       if (cps->sdKludge) {
14445         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14446       } else {
14447         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14448       }
14449       SendToProgram(buf, cps);
14450     }
14451
14452     if(cps->nps >= 0) { /* [HGM] nps */
14453         if(cps->supportsNPS == FALSE)
14454           cps->nps = -1; // don't use if engine explicitly says not supported!
14455         else {
14456           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14457           SendToProgram(buf, cps);
14458         }
14459     }
14460 }
14461
14462 ChessProgramState *WhitePlayer()
14463 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14464 {
14465     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14466        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14467         return &second;
14468     return &first;
14469 }
14470
14471 void
14472 SendTimeRemaining(cps, machineWhite)
14473      ChessProgramState *cps;
14474      int /*boolean*/ machineWhite;
14475 {
14476     char message[MSG_SIZ];
14477     long time, otime;
14478
14479     /* Note: this routine must be called when the clocks are stopped
14480        or when they have *just* been set or switched; otherwise
14481        it will be off by the time since the current tick started.
14482     */
14483     if (machineWhite) {
14484         time = whiteTimeRemaining / 10;
14485         otime = blackTimeRemaining / 10;
14486     } else {
14487         time = blackTimeRemaining / 10;
14488         otime = whiteTimeRemaining / 10;
14489     }
14490     /* [HGM] translate opponent's time by time-odds factor */
14491     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14492     if (appData.debugMode) {
14493         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14494     }
14495
14496     if (time <= 0) time = 1;
14497     if (otime <= 0) otime = 1;
14498
14499     snprintf(message, MSG_SIZ, "time %ld\n", time);
14500     SendToProgram(message, cps);
14501
14502     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14503     SendToProgram(message, cps);
14504 }
14505
14506 int
14507 BoolFeature(p, name, loc, cps)
14508      char **p;
14509      char *name;
14510      int *loc;
14511      ChessProgramState *cps;
14512 {
14513   char buf[MSG_SIZ];
14514   int len = strlen(name);
14515   int val;
14516
14517   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14518     (*p) += len + 1;
14519     sscanf(*p, "%d", &val);
14520     *loc = (val != 0);
14521     while (**p && **p != ' ')
14522       (*p)++;
14523     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14524     SendToProgram(buf, cps);
14525     return TRUE;
14526   }
14527   return FALSE;
14528 }
14529
14530 int
14531 IntFeature(p, name, loc, cps)
14532      char **p;
14533      char *name;
14534      int *loc;
14535      ChessProgramState *cps;
14536 {
14537   char buf[MSG_SIZ];
14538   int len = strlen(name);
14539   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14540     (*p) += len + 1;
14541     sscanf(*p, "%d", loc);
14542     while (**p && **p != ' ') (*p)++;
14543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14544     SendToProgram(buf, cps);
14545     return TRUE;
14546   }
14547   return FALSE;
14548 }
14549
14550 int
14551 StringFeature(p, name, loc, cps)
14552      char **p;
14553      char *name;
14554      char loc[];
14555      ChessProgramState *cps;
14556 {
14557   char buf[MSG_SIZ];
14558   int len = strlen(name);
14559   if (strncmp((*p), name, len) == 0
14560       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14561     (*p) += len + 2;
14562     sscanf(*p, "%[^\"]", loc);
14563     while (**p && **p != '\"') (*p)++;
14564     if (**p == '\"') (*p)++;
14565     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14566     SendToProgram(buf, cps);
14567     return TRUE;
14568   }
14569   return FALSE;
14570 }
14571
14572 int
14573 ParseOption(Option *opt, ChessProgramState *cps)
14574 // [HGM] options: process the string that defines an engine option, and determine
14575 // name, type, default value, and allowed value range
14576 {
14577         char *p, *q, buf[MSG_SIZ];
14578         int n, min = (-1)<<31, max = 1<<31, def;
14579
14580         if(p = strstr(opt->name, " -spin ")) {
14581             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14582             if(max < min) max = min; // enforce consistency
14583             if(def < min) def = min;
14584             if(def > max) def = max;
14585             opt->value = def;
14586             opt->min = min;
14587             opt->max = max;
14588             opt->type = Spin;
14589         } else if((p = strstr(opt->name, " -slider "))) {
14590             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14591             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14592             if(max < min) max = min; // enforce consistency
14593             if(def < min) def = min;
14594             if(def > max) def = max;
14595             opt->value = def;
14596             opt->min = min;
14597             opt->max = max;
14598             opt->type = Spin; // Slider;
14599         } else if((p = strstr(opt->name, " -string "))) {
14600             opt->textValue = p+9;
14601             opt->type = TextBox;
14602         } else if((p = strstr(opt->name, " -file "))) {
14603             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14604             opt->textValue = p+7;
14605             opt->type = FileName; // FileName;
14606         } else if((p = strstr(opt->name, " -path "))) {
14607             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14608             opt->textValue = p+7;
14609             opt->type = PathName; // PathName;
14610         } else if(p = strstr(opt->name, " -check ")) {
14611             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14612             opt->value = (def != 0);
14613             opt->type = CheckBox;
14614         } else if(p = strstr(opt->name, " -combo ")) {
14615             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14616             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14617             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14618             opt->value = n = 0;
14619             while(q = StrStr(q, " /// ")) {
14620                 n++; *q = 0;    // count choices, and null-terminate each of them
14621                 q += 5;
14622                 if(*q == '*') { // remember default, which is marked with * prefix
14623                     q++;
14624                     opt->value = n;
14625                 }
14626                 cps->comboList[cps->comboCnt++] = q;
14627             }
14628             cps->comboList[cps->comboCnt++] = NULL;
14629             opt->max = n + 1;
14630             opt->type = ComboBox;
14631         } else if(p = strstr(opt->name, " -button")) {
14632             opt->type = Button;
14633         } else if(p = strstr(opt->name, " -save")) {
14634             opt->type = SaveButton;
14635         } else return FALSE;
14636         *p = 0; // terminate option name
14637         // now look if the command-line options define a setting for this engine option.
14638         if(cps->optionSettings && cps->optionSettings[0])
14639             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14640         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14641           snprintf(buf, MSG_SIZ, "option %s", p);
14642                 if(p = strstr(buf, ",")) *p = 0;
14643                 if(q = strchr(buf, '=')) switch(opt->type) {
14644                     case ComboBox:
14645                         for(n=0; n<opt->max; n++)
14646                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14647                         break;
14648                     case TextBox:
14649                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14650                         break;
14651                     case Spin:
14652                     case CheckBox:
14653                         opt->value = atoi(q+1);
14654                     default:
14655                         break;
14656                 }
14657                 strcat(buf, "\n");
14658                 SendToProgram(buf, cps);
14659         }
14660         return TRUE;
14661 }
14662
14663 void
14664 FeatureDone(cps, val)
14665      ChessProgramState* cps;
14666      int val;
14667 {
14668   DelayedEventCallback cb = GetDelayedEvent();
14669   if ((cb == InitBackEnd3 && cps == &first) ||
14670       (cb == SettingsMenuIfReady && cps == &second) ||
14671       (cb == LoadEngine) ||
14672       (cb == TwoMachinesEventIfReady)) {
14673     CancelDelayedEvent();
14674     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14675   }
14676   cps->initDone = val;
14677 }
14678
14679 /* Parse feature command from engine */
14680 void
14681 ParseFeatures(args, cps)
14682      char* args;
14683      ChessProgramState *cps;
14684 {
14685   char *p = args;
14686   char *q;
14687   int val;
14688   char buf[MSG_SIZ];
14689
14690   for (;;) {
14691     while (*p == ' ') p++;
14692     if (*p == NULLCHAR) return;
14693
14694     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14695     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14696     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14697     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14698     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14699     if (BoolFeature(&p, "reuse", &val, cps)) {
14700       /* Engine can disable reuse, but can't enable it if user said no */
14701       if (!val) cps->reuse = FALSE;
14702       continue;
14703     }
14704     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14705     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14706       if (gameMode == TwoMachinesPlay) {
14707         DisplayTwoMachinesTitle();
14708       } else {
14709         DisplayTitle("");
14710       }
14711       continue;
14712     }
14713     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14714     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14715     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14716     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14717     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14718     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14719     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14720     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14721     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14722     if (IntFeature(&p, "done", &val, cps)) {
14723       FeatureDone(cps, val);
14724       continue;
14725     }
14726     /* Added by Tord: */
14727     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14728     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14729     /* End of additions by Tord */
14730
14731     /* [HGM] added features: */
14732     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14733     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14734     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14735     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14736     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14737     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14738     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14739         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14740           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14741             SendToProgram(buf, cps);
14742             continue;
14743         }
14744         if(cps->nrOptions >= MAX_OPTIONS) {
14745             cps->nrOptions--;
14746             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14747             DisplayError(buf, 0);
14748         }
14749         continue;
14750     }
14751     /* End of additions by HGM */
14752
14753     /* unknown feature: complain and skip */
14754     q = p;
14755     while (*q && *q != '=') q++;
14756     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14757     SendToProgram(buf, cps);
14758     p = q;
14759     if (*p == '=') {
14760       p++;
14761       if (*p == '\"') {
14762         p++;
14763         while (*p && *p != '\"') p++;
14764         if (*p == '\"') p++;
14765       } else {
14766         while (*p && *p != ' ') p++;
14767       }
14768     }
14769   }
14770
14771 }
14772
14773 void
14774 PeriodicUpdatesEvent(newState)
14775      int newState;
14776 {
14777     if (newState == appData.periodicUpdates)
14778       return;
14779
14780     appData.periodicUpdates=newState;
14781
14782     /* Display type changes, so update it now */
14783 //    DisplayAnalysis();
14784
14785     /* Get the ball rolling again... */
14786     if (newState) {
14787         AnalysisPeriodicEvent(1);
14788         StartAnalysisClock();
14789     }
14790 }
14791
14792 void
14793 PonderNextMoveEvent(newState)
14794      int newState;
14795 {
14796     if (newState == appData.ponderNextMove) return;
14797     if (gameMode == EditPosition) EditPositionDone(TRUE);
14798     if (newState) {
14799         SendToProgram("hard\n", &first);
14800         if (gameMode == TwoMachinesPlay) {
14801             SendToProgram("hard\n", &second);
14802         }
14803     } else {
14804         SendToProgram("easy\n", &first);
14805         thinkOutput[0] = NULLCHAR;
14806         if (gameMode == TwoMachinesPlay) {
14807             SendToProgram("easy\n", &second);
14808         }
14809     }
14810     appData.ponderNextMove = newState;
14811 }
14812
14813 void
14814 NewSettingEvent(option, feature, command, value)
14815      char *command;
14816      int option, value, *feature;
14817 {
14818     char buf[MSG_SIZ];
14819
14820     if (gameMode == EditPosition) EditPositionDone(TRUE);
14821     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14822     if(feature == NULL || *feature) SendToProgram(buf, &first);
14823     if (gameMode == TwoMachinesPlay) {
14824         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14825     }
14826 }
14827
14828 void
14829 ShowThinkingEvent()
14830 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14831 {
14832     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14833     int newState = appData.showThinking
14834         // [HGM] thinking: other features now need thinking output as well
14835         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14836
14837     if (oldState == newState) return;
14838     oldState = newState;
14839     if (gameMode == EditPosition) EditPositionDone(TRUE);
14840     if (oldState) {
14841         SendToProgram("post\n", &first);
14842         if (gameMode == TwoMachinesPlay) {
14843             SendToProgram("post\n", &second);
14844         }
14845     } else {
14846         SendToProgram("nopost\n", &first);
14847         thinkOutput[0] = NULLCHAR;
14848         if (gameMode == TwoMachinesPlay) {
14849             SendToProgram("nopost\n", &second);
14850         }
14851     }
14852 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14853 }
14854
14855 void
14856 AskQuestionEvent(title, question, replyPrefix, which)
14857      char *title; char *question; char *replyPrefix; char *which;
14858 {
14859   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14860   if (pr == NoProc) return;
14861   AskQuestion(title, question, replyPrefix, pr);
14862 }
14863
14864 void
14865 TypeInEvent(char firstChar)
14866 {
14867     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14868         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14869         gameMode == AnalyzeMode || gameMode == EditGame || \r
14870         gameMode == EditPosition || gameMode == IcsExamining ||\r
14871         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14872         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14873                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14874                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14875         gameMode == Training) PopUpMoveDialog(firstChar);
14876 }
14877
14878 void
14879 TypeInDoneEvent(char *move)
14880 {
14881         Board board;
14882         int n, fromX, fromY, toX, toY;
14883         char promoChar;
14884         ChessMove moveType;\r
14885
14886         // [HGM] FENedit\r
14887         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14888                 EditPositionPasteFEN(move);\r
14889                 return;\r
14890         }\r
14891         // [HGM] movenum: allow move number to be typed in any mode\r
14892         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14893           ToNrEvent(2*n-1);\r
14894           return;\r
14895         }\r
14896
14897       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14898         gameMode != Training) {\r
14899         DisplayMoveError(_("Displayed move is not current"));\r
14900       } else {\r
14901         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14902           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14903         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14904         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14905           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14906           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14907         } else {\r
14908           DisplayMoveError(_("Could not parse move"));\r
14909         }
14910       }\r
14911 }\r
14912
14913 void
14914 DisplayMove(moveNumber)
14915      int moveNumber;
14916 {
14917     char message[MSG_SIZ];
14918     char res[MSG_SIZ];
14919     char cpThinkOutput[MSG_SIZ];
14920
14921     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14922
14923     if (moveNumber == forwardMostMove - 1 ||
14924         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14925
14926         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14927
14928         if (strchr(cpThinkOutput, '\n')) {
14929             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14930         }
14931     } else {
14932         *cpThinkOutput = NULLCHAR;
14933     }
14934
14935     /* [AS] Hide thinking from human user */
14936     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14937         *cpThinkOutput = NULLCHAR;
14938         if( thinkOutput[0] != NULLCHAR ) {
14939             int i;
14940
14941             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14942                 cpThinkOutput[i] = '.';
14943             }
14944             cpThinkOutput[i] = NULLCHAR;
14945             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14946         }
14947     }
14948
14949     if (moveNumber == forwardMostMove - 1 &&
14950         gameInfo.resultDetails != NULL) {
14951         if (gameInfo.resultDetails[0] == NULLCHAR) {
14952           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14953         } else {
14954           snprintf(res, MSG_SIZ, " {%s} %s",
14955                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14956         }
14957     } else {
14958         res[0] = NULLCHAR;
14959     }
14960
14961     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14962         DisplayMessage(res, cpThinkOutput);
14963     } else {
14964       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14965                 WhiteOnMove(moveNumber) ? " " : ".. ",
14966                 parseList[moveNumber], res);
14967         DisplayMessage(message, cpThinkOutput);
14968     }
14969 }
14970
14971 void
14972 DisplayComment(moveNumber, text)
14973      int moveNumber;
14974      char *text;
14975 {
14976     char title[MSG_SIZ];
14977     char buf[8000]; // comment can be long!
14978     int score, depth;
14979
14980     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14981       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14982     } else {
14983       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14984               WhiteOnMove(moveNumber) ? " " : ".. ",
14985               parseList[moveNumber]);
14986     }
14987     // [HGM] PV info: display PV info together with (or as) comment
14988     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14989       if(text == NULL) text = "";
14990       score = pvInfoList[moveNumber].score;
14991       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14992               depth, (pvInfoList[moveNumber].time+50)/100, text);
14993       text = buf;
14994     }
14995     if (text != NULL && (appData.autoDisplayComment || commentUp))
14996         CommentPopUp(title, text);
14997 }
14998
14999 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15000  * might be busy thinking or pondering.  It can be omitted if your
15001  * gnuchess is configured to stop thinking immediately on any user
15002  * input.  However, that gnuchess feature depends on the FIONREAD
15003  * ioctl, which does not work properly on some flavors of Unix.
15004  */
15005 void
15006 Attention(cps)
15007      ChessProgramState *cps;
15008 {
15009 #if ATTENTION
15010     if (!cps->useSigint) return;
15011     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15012     switch (gameMode) {
15013       case MachinePlaysWhite:
15014       case MachinePlaysBlack:
15015       case TwoMachinesPlay:
15016       case IcsPlayingWhite:
15017       case IcsPlayingBlack:
15018       case AnalyzeMode:
15019       case AnalyzeFile:
15020         /* Skip if we know it isn't thinking */
15021         if (!cps->maybeThinking) return;
15022         if (appData.debugMode)
15023           fprintf(debugFP, "Interrupting %s\n", cps->which);
15024         InterruptChildProcess(cps->pr);
15025         cps->maybeThinking = FALSE;
15026         break;
15027       default:
15028         break;
15029     }
15030 #endif /*ATTENTION*/
15031 }
15032
15033 int
15034 CheckFlags()
15035 {
15036     if (whiteTimeRemaining <= 0) {
15037         if (!whiteFlag) {
15038             whiteFlag = TRUE;
15039             if (appData.icsActive) {
15040                 if (appData.autoCallFlag &&
15041                     gameMode == IcsPlayingBlack && !blackFlag) {
15042                   SendToICS(ics_prefix);
15043                   SendToICS("flag\n");
15044                 }
15045             } else {
15046                 if (blackFlag) {
15047                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15048                 } else {
15049                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15050                     if (appData.autoCallFlag) {
15051                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15052                         return TRUE;
15053                     }
15054                 }
15055             }
15056         }
15057     }
15058     if (blackTimeRemaining <= 0) {
15059         if (!blackFlag) {
15060             blackFlag = TRUE;
15061             if (appData.icsActive) {
15062                 if (appData.autoCallFlag &&
15063                     gameMode == IcsPlayingWhite && !whiteFlag) {
15064                   SendToICS(ics_prefix);
15065                   SendToICS("flag\n");
15066                 }
15067             } else {
15068                 if (whiteFlag) {
15069                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15070                 } else {
15071                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15072                     if (appData.autoCallFlag) {
15073                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15074                         return TRUE;
15075                     }
15076                 }
15077             }
15078         }
15079     }
15080     return FALSE;
15081 }
15082
15083 void
15084 CheckTimeControl()
15085 {
15086     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15087         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15088
15089     /*
15090      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15091      */
15092     if ( !WhiteOnMove(forwardMostMove) ) {
15093         /* White made time control */
15094         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15095         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15096         /* [HGM] time odds: correct new time quota for time odds! */
15097                                             / WhitePlayer()->timeOdds;
15098         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15099     } else {
15100         lastBlack -= blackTimeRemaining;
15101         /* Black made time control */
15102         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15103                                             / WhitePlayer()->other->timeOdds;
15104         lastWhite = whiteTimeRemaining;
15105     }
15106 }
15107
15108 void
15109 DisplayBothClocks()
15110 {
15111     int wom = gameMode == EditPosition ?
15112       !blackPlaysFirst : WhiteOnMove(currentMove);
15113     DisplayWhiteClock(whiteTimeRemaining, wom);
15114     DisplayBlackClock(blackTimeRemaining, !wom);
15115 }
15116
15117
15118 /* Timekeeping seems to be a portability nightmare.  I think everyone
15119    has ftime(), but I'm really not sure, so I'm including some ifdefs
15120    to use other calls if you don't.  Clocks will be less accurate if
15121    you have neither ftime nor gettimeofday.
15122 */
15123
15124 /* VS 2008 requires the #include outside of the function */
15125 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15126 #include <sys/timeb.h>
15127 #endif
15128
15129 /* Get the current time as a TimeMark */
15130 void
15131 GetTimeMark(tm)
15132      TimeMark *tm;
15133 {
15134 #if HAVE_GETTIMEOFDAY
15135
15136     struct timeval timeVal;
15137     struct timezone timeZone;
15138
15139     gettimeofday(&timeVal, &timeZone);
15140     tm->sec = (long) timeVal.tv_sec;
15141     tm->ms = (int) (timeVal.tv_usec / 1000L);
15142
15143 #else /*!HAVE_GETTIMEOFDAY*/
15144 #if HAVE_FTIME
15145
15146 // include <sys/timeb.h> / moved to just above start of function
15147     struct timeb timeB;
15148
15149     ftime(&timeB);
15150     tm->sec = (long) timeB.time;
15151     tm->ms = (int) timeB.millitm;
15152
15153 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15154     tm->sec = (long) time(NULL);
15155     tm->ms = 0;
15156 #endif
15157 #endif
15158 }
15159
15160 /* Return the difference in milliseconds between two
15161    time marks.  We assume the difference will fit in a long!
15162 */
15163 long
15164 SubtractTimeMarks(tm2, tm1)
15165      TimeMark *tm2, *tm1;
15166 {
15167     return 1000L*(tm2->sec - tm1->sec) +
15168            (long) (tm2->ms - tm1->ms);
15169 }
15170
15171
15172 /*
15173  * Code to manage the game clocks.
15174  *
15175  * In tournament play, black starts the clock and then white makes a move.
15176  * We give the human user a slight advantage if he is playing white---the
15177  * clocks don't run until he makes his first move, so it takes zero time.
15178  * Also, we don't account for network lag, so we could get out of sync
15179  * with GNU Chess's clock -- but then, referees are always right.
15180  */
15181
15182 static TimeMark tickStartTM;
15183 static long intendedTickLength;
15184
15185 long
15186 NextTickLength(timeRemaining)
15187      long timeRemaining;
15188 {
15189     long nominalTickLength, nextTickLength;
15190
15191     if (timeRemaining > 0L && timeRemaining <= 10000L)
15192       nominalTickLength = 100L;
15193     else
15194       nominalTickLength = 1000L;
15195     nextTickLength = timeRemaining % nominalTickLength;
15196     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15197
15198     return nextTickLength;
15199 }
15200
15201 /* Adjust clock one minute up or down */
15202 void
15203 AdjustClock(Boolean which, int dir)
15204 {
15205     if(which) blackTimeRemaining += 60000*dir;
15206     else      whiteTimeRemaining += 60000*dir;
15207     DisplayBothClocks();
15208 }
15209
15210 /* Stop clocks and reset to a fresh time control */
15211 void
15212 ResetClocks()
15213 {
15214     (void) StopClockTimer();
15215     if (appData.icsActive) {
15216         whiteTimeRemaining = blackTimeRemaining = 0;
15217     } else if (searchTime) {
15218         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15219         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15220     } else { /* [HGM] correct new time quote for time odds */
15221         whiteTC = blackTC = fullTimeControlString;
15222         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15223         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15224     }
15225     if (whiteFlag || blackFlag) {
15226         DisplayTitle("");
15227         whiteFlag = blackFlag = FALSE;
15228     }
15229     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15230     DisplayBothClocks();
15231 }
15232
15233 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15234
15235 /* Decrement running clock by amount of time that has passed */
15236 void
15237 DecrementClocks()
15238 {
15239     long timeRemaining;
15240     long lastTickLength, fudge;
15241     TimeMark now;
15242
15243     if (!appData.clockMode) return;
15244     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15245
15246     GetTimeMark(&now);
15247
15248     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15249
15250     /* Fudge if we woke up a little too soon */
15251     fudge = intendedTickLength - lastTickLength;
15252     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15253
15254     if (WhiteOnMove(forwardMostMove)) {
15255         if(whiteNPS >= 0) lastTickLength = 0;
15256         timeRemaining = whiteTimeRemaining -= lastTickLength;
15257         if(timeRemaining < 0 && !appData.icsActive) {
15258             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15259             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15260                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15261                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15262             }
15263         }
15264         DisplayWhiteClock(whiteTimeRemaining - fudge,
15265                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15266     } else {
15267         if(blackNPS >= 0) lastTickLength = 0;
15268         timeRemaining = blackTimeRemaining -= lastTickLength;
15269         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15270             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15271             if(suddenDeath) {
15272                 blackStartMove = forwardMostMove;
15273                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15274             }
15275         }
15276         DisplayBlackClock(blackTimeRemaining - fudge,
15277                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15278     }
15279     if (CheckFlags()) return;
15280
15281     tickStartTM = now;
15282     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15283     StartClockTimer(intendedTickLength);
15284
15285     /* if the time remaining has fallen below the alarm threshold, sound the
15286      * alarm. if the alarm has sounded and (due to a takeback or time control
15287      * with increment) the time remaining has increased to a level above the
15288      * threshold, reset the alarm so it can sound again.
15289      */
15290
15291     if (appData.icsActive && appData.icsAlarm) {
15292
15293         /* make sure we are dealing with the user's clock */
15294         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15295                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15296            )) return;
15297
15298         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15299             alarmSounded = FALSE;
15300         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15301             PlayAlarmSound();
15302             alarmSounded = TRUE;
15303         }
15304     }
15305 }
15306
15307
15308 /* A player has just moved, so stop the previously running
15309    clock and (if in clock mode) start the other one.
15310    We redisplay both clocks in case we're in ICS mode, because
15311    ICS gives us an update to both clocks after every move.
15312    Note that this routine is called *after* forwardMostMove
15313    is updated, so the last fractional tick must be subtracted
15314    from the color that is *not* on move now.
15315 */
15316 void
15317 SwitchClocks(int newMoveNr)
15318 {
15319     long lastTickLength;
15320     TimeMark now;
15321     int flagged = FALSE;
15322
15323     GetTimeMark(&now);
15324
15325     if (StopClockTimer() && appData.clockMode) {
15326         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15327         if (!WhiteOnMove(forwardMostMove)) {
15328             if(blackNPS >= 0) lastTickLength = 0;
15329             blackTimeRemaining -= lastTickLength;
15330            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15331 //         if(pvInfoList[forwardMostMove].time == -1)
15332                  pvInfoList[forwardMostMove].time =               // use GUI time
15333                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15334         } else {
15335            if(whiteNPS >= 0) lastTickLength = 0;
15336            whiteTimeRemaining -= lastTickLength;
15337            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15338 //         if(pvInfoList[forwardMostMove].time == -1)
15339                  pvInfoList[forwardMostMove].time =
15340                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15341         }
15342         flagged = CheckFlags();
15343     }
15344     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15345     CheckTimeControl();
15346
15347     if (flagged || !appData.clockMode) return;
15348
15349     switch (gameMode) {
15350       case MachinePlaysBlack:
15351       case MachinePlaysWhite:
15352       case BeginningOfGame:
15353         if (pausing) return;
15354         break;
15355
15356       case EditGame:
15357       case PlayFromGameFile:
15358       case IcsExamining:
15359         return;
15360
15361       default:
15362         break;
15363     }
15364
15365     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15366         if(WhiteOnMove(forwardMostMove))
15367              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15368         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15369     }
15370
15371     tickStartTM = now;
15372     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15373       whiteTimeRemaining : blackTimeRemaining);
15374     StartClockTimer(intendedTickLength);
15375 }
15376
15377
15378 /* Stop both clocks */
15379 void
15380 StopClocks()
15381 {
15382     long lastTickLength;
15383     TimeMark now;
15384
15385     if (!StopClockTimer()) return;
15386     if (!appData.clockMode) return;
15387
15388     GetTimeMark(&now);
15389
15390     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15391     if (WhiteOnMove(forwardMostMove)) {
15392         if(whiteNPS >= 0) lastTickLength = 0;
15393         whiteTimeRemaining -= lastTickLength;
15394         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15395     } else {
15396         if(blackNPS >= 0) lastTickLength = 0;
15397         blackTimeRemaining -= lastTickLength;
15398         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15399     }
15400     CheckFlags();
15401 }
15402
15403 /* Start clock of player on move.  Time may have been reset, so
15404    if clock is already running, stop and restart it. */
15405 void
15406 StartClocks()
15407 {
15408     (void) StopClockTimer(); /* in case it was running already */
15409     DisplayBothClocks();
15410     if (CheckFlags()) return;
15411
15412     if (!appData.clockMode) return;
15413     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15414
15415     GetTimeMark(&tickStartTM);
15416     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15417       whiteTimeRemaining : blackTimeRemaining);
15418
15419    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15420     whiteNPS = blackNPS = -1;
15421     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15422        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15423         whiteNPS = first.nps;
15424     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15425        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15426         blackNPS = first.nps;
15427     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15428         whiteNPS = second.nps;
15429     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15430         blackNPS = second.nps;
15431     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15432
15433     StartClockTimer(intendedTickLength);
15434 }
15435
15436 char *
15437 TimeString(ms)
15438      long ms;
15439 {
15440     long second, minute, hour, day;
15441     char *sign = "";
15442     static char buf[32];
15443
15444     if (ms > 0 && ms <= 9900) {
15445       /* convert milliseconds to tenths, rounding up */
15446       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15447
15448       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15449       return buf;
15450     }
15451
15452     /* convert milliseconds to seconds, rounding up */
15453     /* use floating point to avoid strangeness of integer division
15454        with negative dividends on many machines */
15455     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15456
15457     if (second < 0) {
15458         sign = "-";
15459         second = -second;
15460     }
15461
15462     day = second / (60 * 60 * 24);
15463     second = second % (60 * 60 * 24);
15464     hour = second / (60 * 60);
15465     second = second % (60 * 60);
15466     minute = second / 60;
15467     second = second % 60;
15468
15469     if (day > 0)
15470       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15471               sign, day, hour, minute, second);
15472     else if (hour > 0)
15473       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15474     else
15475       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15476
15477     return buf;
15478 }
15479
15480
15481 /*
15482  * This is necessary because some C libraries aren't ANSI C compliant yet.
15483  */
15484 char *
15485 StrStr(string, match)
15486      char *string, *match;
15487 {
15488     int i, length;
15489
15490     length = strlen(match);
15491
15492     for (i = strlen(string) - length; i >= 0; i--, string++)
15493       if (!strncmp(match, string, length))
15494         return string;
15495
15496     return NULL;
15497 }
15498
15499 char *
15500 StrCaseStr(string, match)
15501      char *string, *match;
15502 {
15503     int i, j, length;
15504
15505     length = strlen(match);
15506
15507     for (i = strlen(string) - length; i >= 0; i--, string++) {
15508         for (j = 0; j < length; j++) {
15509             if (ToLower(match[j]) != ToLower(string[j]))
15510               break;
15511         }
15512         if (j == length) return string;
15513     }
15514
15515     return NULL;
15516 }
15517
15518 #ifndef _amigados
15519 int
15520 StrCaseCmp(s1, s2)
15521      char *s1, *s2;
15522 {
15523     char c1, c2;
15524
15525     for (;;) {
15526         c1 = ToLower(*s1++);
15527         c2 = ToLower(*s2++);
15528         if (c1 > c2) return 1;
15529         if (c1 < c2) return -1;
15530         if (c1 == NULLCHAR) return 0;
15531     }
15532 }
15533
15534
15535 int
15536 ToLower(c)
15537      int c;
15538 {
15539     return isupper(c) ? tolower(c) : c;
15540 }
15541
15542
15543 int
15544 ToUpper(c)
15545      int c;
15546 {
15547     return islower(c) ? toupper(c) : c;
15548 }
15549 #endif /* !_amigados    */
15550
15551 char *
15552 StrSave(s)
15553      char *s;
15554 {
15555   char *ret;
15556
15557   if ((ret = (char *) malloc(strlen(s) + 1)))
15558     {
15559       safeStrCpy(ret, s, strlen(s)+1);
15560     }
15561   return ret;
15562 }
15563
15564 char *
15565 StrSavePtr(s, savePtr)
15566      char *s, **savePtr;
15567 {
15568     if (*savePtr) {
15569         free(*savePtr);
15570     }
15571     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15572       safeStrCpy(*savePtr, s, strlen(s)+1);
15573     }
15574     return(*savePtr);
15575 }
15576
15577 char *
15578 PGNDate()
15579 {
15580     time_t clock;
15581     struct tm *tm;
15582     char buf[MSG_SIZ];
15583
15584     clock = time((time_t *)NULL);
15585     tm = localtime(&clock);
15586     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15587             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15588     return StrSave(buf);
15589 }
15590
15591
15592 char *
15593 PositionToFEN(move, overrideCastling)
15594      int move;
15595      char *overrideCastling;
15596 {
15597     int i, j, fromX, fromY, toX, toY;
15598     int whiteToPlay;
15599     char buf[128];
15600     char *p, *q;
15601     int emptycount;
15602     ChessSquare piece;
15603
15604     whiteToPlay = (gameMode == EditPosition) ?
15605       !blackPlaysFirst : (move % 2 == 0);
15606     p = buf;
15607
15608     /* Piece placement data */
15609     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15610         emptycount = 0;
15611         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15612             if (boards[move][i][j] == EmptySquare) {
15613                 emptycount++;
15614             } else { ChessSquare piece = boards[move][i][j];
15615                 if (emptycount > 0) {
15616                     if(emptycount<10) /* [HGM] can be >= 10 */
15617                         *p++ = '0' + emptycount;
15618                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15619                     emptycount = 0;
15620                 }
15621                 if(PieceToChar(piece) == '+') {
15622                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15623                     *p++ = '+';
15624                     piece = (ChessSquare)(DEMOTED piece);
15625                 }
15626                 *p++ = PieceToChar(piece);
15627                 if(p[-1] == '~') {
15628                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15629                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15630                     *p++ = '~';
15631                 }
15632             }
15633         }
15634         if (emptycount > 0) {
15635             if(emptycount<10) /* [HGM] can be >= 10 */
15636                 *p++ = '0' + emptycount;
15637             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15638             emptycount = 0;
15639         }
15640         *p++ = '/';
15641     }
15642     *(p - 1) = ' ';
15643
15644     /* [HGM] print Crazyhouse or Shogi holdings */
15645     if( gameInfo.holdingsWidth ) {
15646         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15647         q = p;
15648         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15649             piece = boards[move][i][BOARD_WIDTH-1];
15650             if( piece != EmptySquare )
15651               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15652                   *p++ = PieceToChar(piece);
15653         }
15654         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15655             piece = boards[move][BOARD_HEIGHT-i-1][0];
15656             if( piece != EmptySquare )
15657               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15658                   *p++ = PieceToChar(piece);
15659         }
15660
15661         if( q == p ) *p++ = '-';
15662         *p++ = ']';
15663         *p++ = ' ';
15664     }
15665
15666     /* Active color */
15667     *p++ = whiteToPlay ? 'w' : 'b';
15668     *p++ = ' ';
15669
15670   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15671     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15672   } else {
15673   if(nrCastlingRights) {
15674      q = p;
15675      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15676        /* [HGM] write directly from rights */
15677            if(boards[move][CASTLING][2] != NoRights &&
15678               boards[move][CASTLING][0] != NoRights   )
15679                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15680            if(boards[move][CASTLING][2] != NoRights &&
15681               boards[move][CASTLING][1] != NoRights   )
15682                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15683            if(boards[move][CASTLING][5] != NoRights &&
15684               boards[move][CASTLING][3] != NoRights   )
15685                 *p++ = boards[move][CASTLING][3] + AAA;
15686            if(boards[move][CASTLING][5] != NoRights &&
15687               boards[move][CASTLING][4] != NoRights   )
15688                 *p++ = boards[move][CASTLING][4] + AAA;
15689      } else {
15690
15691         /* [HGM] write true castling rights */
15692         if( nrCastlingRights == 6 ) {
15693             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15694                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15695             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15696                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15697             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15698                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15699             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15700                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15701         }
15702      }
15703      if (q == p) *p++ = '-'; /* No castling rights */
15704      *p++ = ' ';
15705   }
15706
15707   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15708      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15709     /* En passant target square */
15710     if (move > backwardMostMove) {
15711         fromX = moveList[move - 1][0] - AAA;
15712         fromY = moveList[move - 1][1] - ONE;
15713         toX = moveList[move - 1][2] - AAA;
15714         toY = moveList[move - 1][3] - ONE;
15715         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15716             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15717             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15718             fromX == toX) {
15719             /* 2-square pawn move just happened */
15720             *p++ = toX + AAA;
15721             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15722         } else {
15723             *p++ = '-';
15724         }
15725     } else if(move == backwardMostMove) {
15726         // [HGM] perhaps we should always do it like this, and forget the above?
15727         if((signed char)boards[move][EP_STATUS] >= 0) {
15728             *p++ = boards[move][EP_STATUS] + AAA;
15729             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15730         } else {
15731             *p++ = '-';
15732         }
15733     } else {
15734         *p++ = '-';
15735     }
15736     *p++ = ' ';
15737   }
15738   }
15739
15740     /* [HGM] find reversible plies */
15741     {   int i = 0, j=move;
15742
15743         if (appData.debugMode) { int k;
15744             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15745             for(k=backwardMostMove; k<=forwardMostMove; k++)
15746                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15747
15748         }
15749
15750         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15751         if( j == backwardMostMove ) i += initialRulePlies;
15752         sprintf(p, "%d ", i);
15753         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15754     }
15755     /* Fullmove number */
15756     sprintf(p, "%d", (move / 2) + 1);
15757
15758     return StrSave(buf);
15759 }
15760
15761 Boolean
15762 ParseFEN(board, blackPlaysFirst, fen)
15763     Board board;
15764      int *blackPlaysFirst;
15765      char *fen;
15766 {
15767     int i, j;
15768     char *p, c;
15769     int emptycount;
15770     ChessSquare piece;
15771
15772     p = fen;
15773
15774     /* [HGM] by default clear Crazyhouse holdings, if present */
15775     if(gameInfo.holdingsWidth) {
15776        for(i=0; i<BOARD_HEIGHT; i++) {
15777            board[i][0]             = EmptySquare; /* black holdings */
15778            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15779            board[i][1]             = (ChessSquare) 0; /* black counts */
15780            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15781        }
15782     }
15783
15784     /* Piece placement data */
15785     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15786         j = 0;
15787         for (;;) {
15788             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15789                 if (*p == '/') p++;
15790                 emptycount = gameInfo.boardWidth - j;
15791                 while (emptycount--)
15792                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15793                 break;
15794 #if(BOARD_FILES >= 10)
15795             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15796                 p++; emptycount=10;
15797                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15798                 while (emptycount--)
15799                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15800 #endif
15801             } else if (isdigit(*p)) {
15802                 emptycount = *p++ - '0';
15803                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15804                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15805                 while (emptycount--)
15806                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15807             } else if (*p == '+' || isalpha(*p)) {
15808                 if (j >= gameInfo.boardWidth) return FALSE;
15809                 if(*p=='+') {
15810                     piece = CharToPiece(*++p);
15811                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15812                     piece = (ChessSquare) (PROMOTED piece ); p++;
15813                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15814                 } else piece = CharToPiece(*p++);
15815
15816                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15817                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15818                     piece = (ChessSquare) (PROMOTED piece);
15819                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15820                     p++;
15821                 }
15822                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15823             } else {
15824                 return FALSE;
15825             }
15826         }
15827     }
15828     while (*p == '/' || *p == ' ') p++;
15829
15830     /* [HGM] look for Crazyhouse holdings here */
15831     while(*p==' ') p++;
15832     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15833         if(*p == '[') p++;
15834         if(*p == '-' ) p++; /* empty holdings */ else {
15835             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15836             /* if we would allow FEN reading to set board size, we would   */
15837             /* have to add holdings and shift the board read so far here   */
15838             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15839                 p++;
15840                 if((int) piece >= (int) BlackPawn ) {
15841                     i = (int)piece - (int)BlackPawn;
15842                     i = PieceToNumber((ChessSquare)i);
15843                     if( i >= gameInfo.holdingsSize ) return FALSE;
15844                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15845                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15846                 } else {
15847                     i = (int)piece - (int)WhitePawn;
15848                     i = PieceToNumber((ChessSquare)i);
15849                     if( i >= gameInfo.holdingsSize ) return FALSE;
15850                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15851                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15852                 }
15853             }
15854         }
15855         if(*p == ']') p++;
15856     }
15857
15858     while(*p == ' ') p++;
15859
15860     /* Active color */
15861     c = *p++;
15862     if(appData.colorNickNames) {
15863       if( c == appData.colorNickNames[0] ) c = 'w'; else
15864       if( c == appData.colorNickNames[1] ) c = 'b';
15865     }
15866     switch (c) {
15867       case 'w':
15868         *blackPlaysFirst = FALSE;
15869         break;
15870       case 'b':
15871         *blackPlaysFirst = TRUE;
15872         break;
15873       default:
15874         return FALSE;
15875     }
15876
15877     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15878     /* return the extra info in global variiables             */
15879
15880     /* set defaults in case FEN is incomplete */
15881     board[EP_STATUS] = EP_UNKNOWN;
15882     for(i=0; i<nrCastlingRights; i++ ) {
15883         board[CASTLING][i] =
15884             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15885     }   /* assume possible unless obviously impossible */
15886     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15887     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15888     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15889                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15890     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15891     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15892     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15893                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15894     FENrulePlies = 0;
15895
15896     while(*p==' ') p++;
15897     if(nrCastlingRights) {
15898       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15899           /* castling indicator present, so default becomes no castlings */
15900           for(i=0; i<nrCastlingRights; i++ ) {
15901                  board[CASTLING][i] = NoRights;
15902           }
15903       }
15904       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15905              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15906              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15907              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15908         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15909
15910         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15911             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15912             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15913         }
15914         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15915             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15916         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15917                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15918         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15919                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15920         switch(c) {
15921           case'K':
15922               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15923               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15924               board[CASTLING][2] = whiteKingFile;
15925               break;
15926           case'Q':
15927               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15928               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15929               board[CASTLING][2] = whiteKingFile;
15930               break;
15931           case'k':
15932               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15933               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15934               board[CASTLING][5] = blackKingFile;
15935               break;
15936           case'q':
15937               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15938               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15939               board[CASTLING][5] = blackKingFile;
15940           case '-':
15941               break;
15942           default: /* FRC castlings */
15943               if(c >= 'a') { /* black rights */
15944                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15945                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15946                   if(i == BOARD_RGHT) break;
15947                   board[CASTLING][5] = i;
15948                   c -= AAA;
15949                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15950                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15951                   if(c > i)
15952                       board[CASTLING][3] = c;
15953                   else
15954                       board[CASTLING][4] = c;
15955               } else { /* white rights */
15956                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15957                     if(board[0][i] == WhiteKing) break;
15958                   if(i == BOARD_RGHT) break;
15959                   board[CASTLING][2] = i;
15960                   c -= AAA - 'a' + 'A';
15961                   if(board[0][c] >= WhiteKing) break;
15962                   if(c > i)
15963                       board[CASTLING][0] = c;
15964                   else
15965                       board[CASTLING][1] = c;
15966               }
15967         }
15968       }
15969       for(i=0; i<nrCastlingRights; i++)
15970         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15971     if (appData.debugMode) {
15972         fprintf(debugFP, "FEN castling rights:");
15973         for(i=0; i<nrCastlingRights; i++)
15974         fprintf(debugFP, " %d", board[CASTLING][i]);
15975         fprintf(debugFP, "\n");
15976     }
15977
15978       while(*p==' ') p++;
15979     }
15980
15981     /* read e.p. field in games that know e.p. capture */
15982     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15983        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15984       if(*p=='-') {
15985         p++; board[EP_STATUS] = EP_NONE;
15986       } else {
15987          char c = *p++ - AAA;
15988
15989          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15990          if(*p >= '0' && *p <='9') p++;
15991          board[EP_STATUS] = c;
15992       }
15993     }
15994
15995
15996     if(sscanf(p, "%d", &i) == 1) {
15997         FENrulePlies = i; /* 50-move ply counter */
15998         /* (The move number is still ignored)    */
15999     }
16000
16001     return TRUE;
16002 }
16003
16004 void
16005 EditPositionPasteFEN(char *fen)
16006 {
16007   if (fen != NULL) {
16008     Board initial_position;
16009
16010     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16011       DisplayError(_("Bad FEN position in clipboard"), 0);
16012       return ;
16013     } else {
16014       int savedBlackPlaysFirst = blackPlaysFirst;
16015       EditPositionEvent();
16016       blackPlaysFirst = savedBlackPlaysFirst;
16017       CopyBoard(boards[0], initial_position);
16018       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16019       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16020       DisplayBothClocks();
16021       DrawPosition(FALSE, boards[currentMove]);
16022     }
16023   }
16024 }
16025
16026 static char cseq[12] = "\\   ";
16027
16028 Boolean set_cont_sequence(char *new_seq)
16029 {
16030     int len;
16031     Boolean ret;
16032
16033     // handle bad attempts to set the sequence
16034         if (!new_seq)
16035                 return 0; // acceptable error - no debug
16036
16037     len = strlen(new_seq);
16038     ret = (len > 0) && (len < sizeof(cseq));
16039     if (ret)
16040       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16041     else if (appData.debugMode)
16042       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16043     return ret;
16044 }
16045
16046 /*
16047     reformat a source message so words don't cross the width boundary.  internal
16048     newlines are not removed.  returns the wrapped size (no null character unless
16049     included in source message).  If dest is NULL, only calculate the size required
16050     for the dest buffer.  lp argument indicats line position upon entry, and it's
16051     passed back upon exit.
16052 */
16053 int wrap(char *dest, char *src, int count, int width, int *lp)
16054 {
16055     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16056
16057     cseq_len = strlen(cseq);
16058     old_line = line = *lp;
16059     ansi = len = clen = 0;
16060
16061     for (i=0; i < count; i++)
16062     {
16063         if (src[i] == '\033')
16064             ansi = 1;
16065
16066         // if we hit the width, back up
16067         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16068         {
16069             // store i & len in case the word is too long
16070             old_i = i, old_len = len;
16071
16072             // find the end of the last word
16073             while (i && src[i] != ' ' && src[i] != '\n')
16074             {
16075                 i--;
16076                 len--;
16077             }
16078
16079             // word too long?  restore i & len before splitting it
16080             if ((old_i-i+clen) >= width)
16081             {
16082                 i = old_i;
16083                 len = old_len;
16084             }
16085
16086             // extra space?
16087             if (i && src[i-1] == ' ')
16088                 len--;
16089
16090             if (src[i] != ' ' && src[i] != '\n')
16091             {
16092                 i--;
16093                 if (len)
16094                     len--;
16095             }
16096
16097             // now append the newline and continuation sequence
16098             if (dest)
16099                 dest[len] = '\n';
16100             len++;
16101             if (dest)
16102                 strncpy(dest+len, cseq, cseq_len);
16103             len += cseq_len;
16104             line = cseq_len;
16105             clen = cseq_len;
16106             continue;
16107         }
16108
16109         if (dest)
16110             dest[len] = src[i];
16111         len++;
16112         if (!ansi)
16113             line++;
16114         if (src[i] == '\n')
16115             line = 0;
16116         if (src[i] == 'm')
16117             ansi = 0;
16118     }
16119     if (dest && appData.debugMode)
16120     {
16121         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16122             count, width, line, len, *lp);
16123         show_bytes(debugFP, src, count);
16124         fprintf(debugFP, "\ndest: ");
16125         show_bytes(debugFP, dest, len);
16126         fprintf(debugFP, "\n");
16127     }
16128     *lp = dest ? line : old_line;
16129
16130     return len;
16131 }
16132
16133 // [HGM] vari: routines for shelving variations
16134
16135 void
16136 PushInner(int firstMove, int lastMove)
16137 {
16138         int i, j, nrMoves = lastMove - firstMove;
16139
16140         // push current tail of game on stack
16141         savedResult[storedGames] = gameInfo.result;
16142         savedDetails[storedGames] = gameInfo.resultDetails;
16143         gameInfo.resultDetails = NULL;
16144         savedFirst[storedGames] = firstMove;
16145         savedLast [storedGames] = lastMove;
16146         savedFramePtr[storedGames] = framePtr;
16147         framePtr -= nrMoves; // reserve space for the boards
16148         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16149             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16150             for(j=0; j<MOVE_LEN; j++)
16151                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16152             for(j=0; j<2*MOVE_LEN; j++)
16153                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16154             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16155             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16156             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16157             pvInfoList[firstMove+i-1].depth = 0;
16158             commentList[framePtr+i] = commentList[firstMove+i];
16159             commentList[firstMove+i] = NULL;
16160         }
16161
16162         storedGames++;
16163         forwardMostMove = firstMove; // truncate game so we can start variation
16164 }
16165
16166 void
16167 PushTail(int firstMove, int lastMove)
16168 {
16169         if(appData.icsActive) { // only in local mode
16170                 forwardMostMove = currentMove; // mimic old ICS behavior
16171                 return;
16172         }
16173         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16174
16175         PushInner(firstMove, lastMove);
16176         if(storedGames == 1) GreyRevert(FALSE);
16177 }
16178
16179 void
16180 PopInner(Boolean annotate)
16181 {
16182         int i, j, nrMoves;
16183         char buf[8000], moveBuf[20];
16184
16185         storedGames--;
16186         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16187         nrMoves = savedLast[storedGames] - currentMove;
16188         if(annotate) {
16189                 int cnt = 10;
16190                 if(!WhiteOnMove(currentMove))
16191                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16192                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16193                 for(i=currentMove; i<forwardMostMove; i++) {
16194                         if(WhiteOnMove(i))
16195                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16196                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16197                         strcat(buf, moveBuf);
16198                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16199                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16200                 }
16201                 strcat(buf, ")");
16202         }
16203         for(i=1; i<=nrMoves; i++) { // copy last variation back
16204             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16205             for(j=0; j<MOVE_LEN; j++)
16206                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16207             for(j=0; j<2*MOVE_LEN; j++)
16208                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16209             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16210             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16211             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16212             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16213             commentList[currentMove+i] = commentList[framePtr+i];
16214             commentList[framePtr+i] = NULL;
16215         }
16216         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16217         framePtr = savedFramePtr[storedGames];
16218         gameInfo.result = savedResult[storedGames];
16219         if(gameInfo.resultDetails != NULL) {
16220             free(gameInfo.resultDetails);
16221       }
16222         gameInfo.resultDetails = savedDetails[storedGames];
16223         forwardMostMove = currentMove + nrMoves;
16224 }
16225
16226 Boolean
16227 PopTail(Boolean annotate)
16228 {
16229         if(appData.icsActive) return FALSE; // only in local mode
16230         if(!storedGames) return FALSE; // sanity
16231         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16232
16233         PopInner(annotate);
16234
16235         if(storedGames == 0) GreyRevert(TRUE);
16236         return TRUE;
16237 }
16238
16239 void
16240 CleanupTail()
16241 {       // remove all shelved variations
16242         int i;
16243         for(i=0; i<storedGames; i++) {
16244             if(savedDetails[i])
16245                 free(savedDetails[i]);
16246             savedDetails[i] = NULL;
16247         }
16248         for(i=framePtr; i<MAX_MOVES; i++) {
16249                 if(commentList[i]) free(commentList[i]);
16250                 commentList[i] = NULL;
16251         }
16252         framePtr = MAX_MOVES-1;
16253         storedGames = 0;
16254 }
16255
16256 void
16257 LoadVariation(int index, char *text)
16258 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16259         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16260         int level = 0, move;
16261
16262         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16263         // first find outermost bracketing variation
16264         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16265             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16266                 if(*p == '{') wait = '}'; else
16267                 if(*p == '[') wait = ']'; else
16268                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16269                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16270             }
16271             if(*p == wait) wait = NULLCHAR; // closing ]} found
16272             p++;
16273         }
16274         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16275         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16276         end[1] = NULLCHAR; // clip off comment beyond variation
16277         ToNrEvent(currentMove-1);
16278         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16279         // kludge: use ParsePV() to append variation to game
16280         move = currentMove;
16281         ParsePV(start, TRUE, TRUE);
16282         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16283         ClearPremoveHighlights();
16284         CommentPopDown();
16285         ToNrEvent(currentMove+1);
16286 }
16287