Obey san feature when sending book moves
[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], *move = bookHit;
7578         if(cps->useSAN) {
7579             int fromX, fromY, toX, toY;
7580             char promoChar;
7581             ChessMove moveType;
7582             move = buf + 30;
7583             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7584                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7585                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7586                                     PosFlags(forwardMostMove),
7587                                     fromY, fromX, toY, toX, promoChar, move);
7588             } else {
7589                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7590                 bookHit = NULL;
7591             }
7592         }
7593         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7594         SendToProgram(buf, cps);
7595         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7596     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7597         SendToProgram("go\n", cps);
7598         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7599     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7600         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7601             SendToProgram("go\n", cps);
7602         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7603     }
7604     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7605 }
7606
7607 char *savedMessage;
7608 ChessProgramState *savedState;
7609 void DeferredBookMove(void)
7610 {
7611         if(savedState->lastPing != savedState->lastPong)
7612                     ScheduleDelayedEvent(DeferredBookMove, 10);
7613         else
7614         HandleMachineMove(savedMessage, savedState);
7615 }
7616
7617 void
7618 HandleMachineMove(message, cps)
7619      char *message;
7620      ChessProgramState *cps;
7621 {
7622     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7623     char realname[MSG_SIZ];
7624     int fromX, fromY, toX, toY;
7625     ChessMove moveType;
7626     char promoChar;
7627     char *p;
7628     int machineWhite;
7629     char *bookHit;
7630
7631     cps->userError = 0;
7632
7633 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7634     /*
7635      * Kludge to ignore BEL characters
7636      */
7637     while (*message == '\007') message++;
7638
7639     /*
7640      * [HGM] engine debug message: ignore lines starting with '#' character
7641      */
7642     if(cps->debug && *message == '#') return;
7643
7644     /*
7645      * Look for book output
7646      */
7647     if (cps == &first && bookRequested) {
7648         if (message[0] == '\t' || message[0] == ' ') {
7649             /* Part of the book output is here; append it */
7650             strcat(bookOutput, message);
7651             strcat(bookOutput, "  \n");
7652             return;
7653         } else if (bookOutput[0] != NULLCHAR) {
7654             /* All of book output has arrived; display it */
7655             char *p = bookOutput;
7656             while (*p != NULLCHAR) {
7657                 if (*p == '\t') *p = ' ';
7658                 p++;
7659             }
7660             DisplayInformation(bookOutput);
7661             bookRequested = FALSE;
7662             /* Fall through to parse the current output */
7663         }
7664     }
7665
7666     /*
7667      * Look for machine move.
7668      */
7669     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7670         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7671     {
7672         /* This method is only useful on engines that support ping */
7673         if (cps->lastPing != cps->lastPong) {
7674           if (gameMode == BeginningOfGame) {
7675             /* Extra move from before last new; ignore */
7676             if (appData.debugMode) {
7677                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7678             }
7679           } else {
7680             if (appData.debugMode) {
7681                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7682                         cps->which, gameMode);
7683             }
7684
7685             SendToProgram("undo\n", cps);
7686           }
7687           return;
7688         }
7689
7690         switch (gameMode) {
7691           case BeginningOfGame:
7692             /* Extra move from before last reset; ignore */
7693             if (appData.debugMode) {
7694                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7695             }
7696             return;
7697
7698           case EndOfGame:
7699           case IcsIdle:
7700           default:
7701             /* Extra move after we tried to stop.  The mode test is
7702                not a reliable way of detecting this problem, but it's
7703                the best we can do on engines that don't support ping.
7704             */
7705             if (appData.debugMode) {
7706                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7707                         cps->which, gameMode);
7708             }
7709             SendToProgram("undo\n", cps);
7710             return;
7711
7712           case MachinePlaysWhite:
7713           case IcsPlayingWhite:
7714             machineWhite = TRUE;
7715             break;
7716
7717           case MachinePlaysBlack:
7718           case IcsPlayingBlack:
7719             machineWhite = FALSE;
7720             break;
7721
7722           case TwoMachinesPlay:
7723             machineWhite = (cps->twoMachinesColor[0] == 'w');
7724             break;
7725         }
7726         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7727             if (appData.debugMode) {
7728                 fprintf(debugFP,
7729                         "Ignoring move out of turn by %s, gameMode %d"
7730                         ", forwardMost %d\n",
7731                         cps->which, gameMode, forwardMostMove);
7732             }
7733             return;
7734         }
7735
7736     if (appData.debugMode) { int f = forwardMostMove;
7737         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7738                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7739                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7740     }
7741         if(cps->alphaRank) AlphaRank(machineMove, 4);
7742         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7743                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7744             /* Machine move could not be parsed; ignore it. */
7745           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7746                     machineMove, _(cps->which));
7747             DisplayError(buf1, 0);
7748             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7749                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7750             if (gameMode == TwoMachinesPlay) {
7751               GameEnds(machineWhite ? BlackWins : WhiteWins,
7752                        buf1, GE_XBOARD);
7753             }
7754             return;
7755         }
7756
7757         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7758         /* So we have to redo legality test with true e.p. status here,  */
7759         /* to make sure an illegal e.p. capture does not slip through,   */
7760         /* to cause a forfeit on a justified illegal-move complaint      */
7761         /* of the opponent.                                              */
7762         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7763            ChessMove moveType;
7764            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7765                              fromY, fromX, toY, toX, promoChar);
7766             if (appData.debugMode) {
7767                 int i;
7768                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7769                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7770                 fprintf(debugFP, "castling rights\n");
7771             }
7772             if(moveType == IllegalMove) {
7773               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7774                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7775                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7776                            buf1, GE_XBOARD);
7777                 return;
7778            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7779            /* [HGM] Kludge to handle engines that send FRC-style castling
7780               when they shouldn't (like TSCP-Gothic) */
7781            switch(moveType) {
7782              case WhiteASideCastleFR:
7783              case BlackASideCastleFR:
7784                toX+=2;
7785                currentMoveString[2]++;
7786                break;
7787              case WhiteHSideCastleFR:
7788              case BlackHSideCastleFR:
7789                toX--;
7790                currentMoveString[2]--;
7791                break;
7792              default: ; // nothing to do, but suppresses warning of pedantic compilers
7793            }
7794         }
7795         hintRequested = FALSE;
7796         lastHint[0] = NULLCHAR;
7797         bookRequested = FALSE;
7798         /* Program may be pondering now */
7799         cps->maybeThinking = TRUE;
7800         if (cps->sendTime == 2) cps->sendTime = 1;
7801         if (cps->offeredDraw) cps->offeredDraw--;
7802
7803         /* [AS] Save move info*/
7804         pvInfoList[ forwardMostMove ].score = programStats.score;
7805         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7806         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7807
7808         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7809
7810         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7811         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7812             int count = 0;
7813
7814             while( count < adjudicateLossPlies ) {
7815                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7816
7817                 if( count & 1 ) {
7818                     score = -score; /* Flip score for winning side */
7819                 }
7820
7821                 if( score > adjudicateLossThreshold ) {
7822                     break;
7823                 }
7824
7825                 count++;
7826             }
7827
7828             if( count >= adjudicateLossPlies ) {
7829                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7830
7831                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7832                     "Xboard adjudication",
7833                     GE_XBOARD );
7834
7835                 return;
7836             }
7837         }
7838
7839         if(Adjudicate(cps)) {
7840             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7841             return; // [HGM] adjudicate: for all automatic game ends
7842         }
7843
7844 #if ZIPPY
7845         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7846             first.initDone) {
7847           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7848                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7849                 SendToICS("draw ");
7850                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7851           }
7852           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7853           ics_user_moved = 1;
7854           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7855                 char buf[3*MSG_SIZ];
7856
7857                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7858                         programStats.score / 100.,
7859                         programStats.depth,
7860                         programStats.time / 100.,
7861                         (unsigned int)programStats.nodes,
7862                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7863                         programStats.movelist);
7864                 SendToICS(buf);
7865 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7866           }
7867         }
7868 #endif
7869
7870         /* [AS] Clear stats for next move */
7871         ClearProgramStats();
7872         thinkOutput[0] = NULLCHAR;
7873         hiddenThinkOutputState = 0;
7874
7875         bookHit = NULL;
7876         if (gameMode == TwoMachinesPlay) {
7877             /* [HGM] relaying draw offers moved to after reception of move */
7878             /* and interpreting offer as claim if it brings draw condition */
7879             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7880                 SendToProgram("draw\n", cps->other);
7881             }
7882             if (cps->other->sendTime) {
7883                 SendTimeRemaining(cps->other,
7884                                   cps->other->twoMachinesColor[0] == 'w');
7885             }
7886             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7887             if (firstMove && !bookHit) {
7888                 firstMove = FALSE;
7889                 if (cps->other->useColors) {
7890                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7891                 }
7892                 SendToProgram("go\n", cps->other);
7893             }
7894             cps->other->maybeThinking = TRUE;
7895         }
7896
7897         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7898
7899         if (!pausing && appData.ringBellAfterMoves) {
7900             RingBell();
7901         }
7902
7903         /*
7904          * Reenable menu items that were disabled while
7905          * machine was thinking
7906          */
7907         if (gameMode != TwoMachinesPlay)
7908             SetUserThinkingEnables();
7909
7910         // [HGM] book: after book hit opponent has received move and is now in force mode
7911         // force the book reply into it, and then fake that it outputted this move by jumping
7912         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7913         if(bookHit) {
7914                 static char bookMove[MSG_SIZ]; // a bit generous?
7915
7916                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7917                 strcat(bookMove, bookHit);
7918                 message = bookMove;
7919                 cps = cps->other;
7920                 programStats.nodes = programStats.depth = programStats.time =
7921                 programStats.score = programStats.got_only_move = 0;
7922                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7923
7924                 if(cps->lastPing != cps->lastPong) {
7925                     savedMessage = message; // args for deferred call
7926                     savedState = cps;
7927                     ScheduleDelayedEvent(DeferredBookMove, 10);
7928                     return;
7929                 }
7930                 goto FakeBookMove;
7931         }
7932
7933         return;
7934     }
7935
7936     /* Set special modes for chess engines.  Later something general
7937      *  could be added here; for now there is just one kludge feature,
7938      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7939      *  when "xboard" is given as an interactive command.
7940      */
7941     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7942         cps->useSigint = FALSE;
7943         cps->useSigterm = FALSE;
7944     }
7945     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7946       ParseFeatures(message+8, cps);
7947       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7948     }
7949
7950     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7951       int dummy, s=6; char buf[MSG_SIZ];
7952       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7953       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7954       ParseFEN(boards[0], &dummy, message+s);
7955       DrawPosition(TRUE, boards[0]);
7956       startedFromSetupPosition = TRUE;
7957       return;
7958     }
7959     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7960      * want this, I was asked to put it in, and obliged.
7961      */
7962     if (!strncmp(message, "setboard ", 9)) {
7963         Board initial_position;
7964
7965         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7966
7967         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7968             DisplayError(_("Bad FEN received from engine"), 0);
7969             return ;
7970         } else {
7971            Reset(TRUE, FALSE);
7972            CopyBoard(boards[0], initial_position);
7973            initialRulePlies = FENrulePlies;
7974            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7975            else gameMode = MachinePlaysBlack;
7976            DrawPosition(FALSE, boards[currentMove]);
7977         }
7978         return;
7979     }
7980
7981     /*
7982      * Look for communication commands
7983      */
7984     if (!strncmp(message, "telluser ", 9)) {
7985         if(message[9] == '\\' && message[10] == '\\')
7986             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7987         DisplayNote(message + 9);
7988         return;
7989     }
7990     if (!strncmp(message, "tellusererror ", 14)) {
7991         cps->userError = 1;
7992         if(message[14] == '\\' && message[15] == '\\')
7993             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7994         DisplayError(message + 14, 0);
7995         return;
7996     }
7997     if (!strncmp(message, "tellopponent ", 13)) {
7998       if (appData.icsActive) {
7999         if (loggedOn) {
8000           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8001           SendToICS(buf1);
8002         }
8003       } else {
8004         DisplayNote(message + 13);
8005       }
8006       return;
8007     }
8008     if (!strncmp(message, "tellothers ", 11)) {
8009       if (appData.icsActive) {
8010         if (loggedOn) {
8011           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8012           SendToICS(buf1);
8013         }
8014       }
8015       return;
8016     }
8017     if (!strncmp(message, "tellall ", 8)) {
8018       if (appData.icsActive) {
8019         if (loggedOn) {
8020           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8021           SendToICS(buf1);
8022         }
8023       } else {
8024         DisplayNote(message + 8);
8025       }
8026       return;
8027     }
8028     if (strncmp(message, "warning", 7) == 0) {
8029         /* Undocumented feature, use tellusererror in new code */
8030         DisplayError(message, 0);
8031         return;
8032     }
8033     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8034         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8035         strcat(realname, " query");
8036         AskQuestion(realname, buf2, buf1, cps->pr);
8037         return;
8038     }
8039     /* Commands from the engine directly to ICS.  We don't allow these to be
8040      *  sent until we are logged on. Crafty kibitzes have been known to
8041      *  interfere with the login process.
8042      */
8043     if (loggedOn) {
8044         if (!strncmp(message, "tellics ", 8)) {
8045             SendToICS(message + 8);
8046             SendToICS("\n");
8047             return;
8048         }
8049         if (!strncmp(message, "tellicsnoalias ", 15)) {
8050             SendToICS(ics_prefix);
8051             SendToICS(message + 15);
8052             SendToICS("\n");
8053             return;
8054         }
8055         /* The following are for backward compatibility only */
8056         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8057             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8058             SendToICS(ics_prefix);
8059             SendToICS(message);
8060             SendToICS("\n");
8061             return;
8062         }
8063     }
8064     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8065         return;
8066     }
8067     /*
8068      * If the move is illegal, cancel it and redraw the board.
8069      * Also deal with other error cases.  Matching is rather loose
8070      * here to accommodate engines written before the spec.
8071      */
8072     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8073         strncmp(message, "Error", 5) == 0) {
8074         if (StrStr(message, "name") ||
8075             StrStr(message, "rating") || StrStr(message, "?") ||
8076             StrStr(message, "result") || StrStr(message, "board") ||
8077             StrStr(message, "bk") || StrStr(message, "computer") ||
8078             StrStr(message, "variant") || StrStr(message, "hint") ||
8079             StrStr(message, "random") || StrStr(message, "depth") ||
8080             StrStr(message, "accepted")) {
8081             return;
8082         }
8083         if (StrStr(message, "protover")) {
8084           /* Program is responding to input, so it's apparently done
8085              initializing, and this error message indicates it is
8086              protocol version 1.  So we don't need to wait any longer
8087              for it to initialize and send feature commands. */
8088           FeatureDone(cps, 1);
8089           cps->protocolVersion = 1;
8090           return;
8091         }
8092         cps->maybeThinking = FALSE;
8093
8094         if (StrStr(message, "draw")) {
8095             /* Program doesn't have "draw" command */
8096             cps->sendDrawOffers = 0;
8097             return;
8098         }
8099         if (cps->sendTime != 1 &&
8100             (StrStr(message, "time") || StrStr(message, "otim"))) {
8101           /* Program apparently doesn't have "time" or "otim" command */
8102           cps->sendTime = 0;
8103           return;
8104         }
8105         if (StrStr(message, "analyze")) {
8106             cps->analysisSupport = FALSE;
8107             cps->analyzing = FALSE;
8108             Reset(FALSE, TRUE);
8109             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8110             DisplayError(buf2, 0);
8111             return;
8112         }
8113         if (StrStr(message, "(no matching move)st")) {
8114           /* Special kludge for GNU Chess 4 only */
8115           cps->stKludge = TRUE;
8116           SendTimeControl(cps, movesPerSession, timeControl,
8117                           timeIncrement, appData.searchDepth,
8118                           searchTime);
8119           return;
8120         }
8121         if (StrStr(message, "(no matching move)sd")) {
8122           /* Special kludge for GNU Chess 4 only */
8123           cps->sdKludge = TRUE;
8124           SendTimeControl(cps, movesPerSession, timeControl,
8125                           timeIncrement, appData.searchDepth,
8126                           searchTime);
8127           return;
8128         }
8129         if (!StrStr(message, "llegal")) {
8130             return;
8131         }
8132         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8133             gameMode == IcsIdle) return;
8134         if (forwardMostMove <= backwardMostMove) return;
8135         if (pausing) PauseEvent();
8136       if(appData.forceIllegal) {
8137             // [HGM] illegal: machine refused move; force position after move into it
8138           SendToProgram("force\n", cps);
8139           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8140                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8141                 // when black is to move, while there might be nothing on a2 or black
8142                 // might already have the move. So send the board as if white has the move.
8143                 // But first we must change the stm of the engine, as it refused the last move
8144                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8145                 if(WhiteOnMove(forwardMostMove)) {
8146                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8147                     SendBoard(cps, forwardMostMove); // kludgeless board
8148                 } else {
8149                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8150                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8151                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8152                 }
8153           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8154             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8155                  gameMode == TwoMachinesPlay)
8156               SendToProgram("go\n", cps);
8157             return;
8158       } else
8159         if (gameMode == PlayFromGameFile) {
8160             /* Stop reading this game file */
8161             gameMode = EditGame;
8162             ModeHighlight();
8163         }
8164         /* [HGM] illegal-move claim should forfeit game when Xboard */
8165         /* only passes fully legal moves                            */
8166         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8167             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8168                                 "False illegal-move claim", GE_XBOARD );
8169             return; // do not take back move we tested as valid
8170         }
8171         currentMove = forwardMostMove-1;
8172         DisplayMove(currentMove-1); /* before DisplayMoveError */
8173         SwitchClocks(forwardMostMove-1); // [HGM] race
8174         DisplayBothClocks();
8175         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8176                 parseList[currentMove], _(cps->which));
8177         DisplayMoveError(buf1);
8178         DrawPosition(FALSE, boards[currentMove]);
8179         return;
8180     }
8181     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8182         /* Program has a broken "time" command that
8183            outputs a string not ending in newline.
8184            Don't use it. */
8185         cps->sendTime = 0;
8186     }
8187
8188     /*
8189      * If chess program startup fails, exit with an error message.
8190      * Attempts to recover here are futile.
8191      */
8192     if ((StrStr(message, "unknown host") != NULL)
8193         || (StrStr(message, "No remote directory") != NULL)
8194         || (StrStr(message, "not found") != NULL)
8195         || (StrStr(message, "No such file") != NULL)
8196         || (StrStr(message, "can't alloc") != NULL)
8197         || (StrStr(message, "Permission denied") != NULL)) {
8198
8199         cps->maybeThinking = FALSE;
8200         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8201                 _(cps->which), cps->program, cps->host, message);
8202         RemoveInputSource(cps->isr);
8203         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8204             if(cps == &first) appData.noChessProgram = TRUE;
8205             DisplayError(buf1, 0);
8206         }
8207         return;
8208     }
8209
8210     /*
8211      * Look for hint output
8212      */
8213     if (sscanf(message, "Hint: %s", buf1) == 1) {
8214         if (cps == &first && hintRequested) {
8215             hintRequested = FALSE;
8216             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8217                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8218                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8219                                     PosFlags(forwardMostMove),
8220                                     fromY, fromX, toY, toX, promoChar, buf1);
8221                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8222                 DisplayInformation(buf2);
8223             } else {
8224                 /* Hint move could not be parsed!? */
8225               snprintf(buf2, sizeof(buf2),
8226                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8227                         buf1, _(cps->which));
8228                 DisplayError(buf2, 0);
8229             }
8230         } else {
8231           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8232         }
8233         return;
8234     }
8235
8236     /*
8237      * Ignore other messages if game is not in progress
8238      */
8239     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8240         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8241
8242     /*
8243      * look for win, lose, draw, or draw offer
8244      */
8245     if (strncmp(message, "1-0", 3) == 0) {
8246         char *p, *q, *r = "";
8247         p = strchr(message, '{');
8248         if (p) {
8249             q = strchr(p, '}');
8250             if (q) {
8251                 *q = NULLCHAR;
8252                 r = p + 1;
8253             }
8254         }
8255         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8256         return;
8257     } else if (strncmp(message, "0-1", 3) == 0) {
8258         char *p, *q, *r = "";
8259         p = strchr(message, '{');
8260         if (p) {
8261             q = strchr(p, '}');
8262             if (q) {
8263                 *q = NULLCHAR;
8264                 r = p + 1;
8265             }
8266         }
8267         /* Kludge for Arasan 4.1 bug */
8268         if (strcmp(r, "Black resigns") == 0) {
8269             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8270             return;
8271         }
8272         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8273         return;
8274     } else if (strncmp(message, "1/2", 3) == 0) {
8275         char *p, *q, *r = "";
8276         p = strchr(message, '{');
8277         if (p) {
8278             q = strchr(p, '}');
8279             if (q) {
8280                 *q = NULLCHAR;
8281                 r = p + 1;
8282             }
8283         }
8284
8285         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8286         return;
8287
8288     } else if (strncmp(message, "White resign", 12) == 0) {
8289         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8290         return;
8291     } else if (strncmp(message, "Black resign", 12) == 0) {
8292         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8293         return;
8294     } else if (strncmp(message, "White matches", 13) == 0 ||
8295                strncmp(message, "Black matches", 13) == 0   ) {
8296         /* [HGM] ignore GNUShogi noises */
8297         return;
8298     } else if (strncmp(message, "White", 5) == 0 &&
8299                message[5] != '(' &&
8300                StrStr(message, "Black") == NULL) {
8301         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8302         return;
8303     } else if (strncmp(message, "Black", 5) == 0 &&
8304                message[5] != '(') {
8305         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8306         return;
8307     } else if (strcmp(message, "resign") == 0 ||
8308                strcmp(message, "computer resigns") == 0) {
8309         switch (gameMode) {
8310           case MachinePlaysBlack:
8311           case IcsPlayingBlack:
8312             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8313             break;
8314           case MachinePlaysWhite:
8315           case IcsPlayingWhite:
8316             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8317             break;
8318           case TwoMachinesPlay:
8319             if (cps->twoMachinesColor[0] == 'w')
8320               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8321             else
8322               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8323             break;
8324           default:
8325             /* can't happen */
8326             break;
8327         }
8328         return;
8329     } else if (strncmp(message, "opponent mates", 14) == 0) {
8330         switch (gameMode) {
8331           case MachinePlaysBlack:
8332           case IcsPlayingBlack:
8333             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8334             break;
8335           case MachinePlaysWhite:
8336           case IcsPlayingWhite:
8337             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8338             break;
8339           case TwoMachinesPlay:
8340             if (cps->twoMachinesColor[0] == 'w')
8341               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8342             else
8343               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8344             break;
8345           default:
8346             /* can't happen */
8347             break;
8348         }
8349         return;
8350     } else if (strncmp(message, "computer mates", 14) == 0) {
8351         switch (gameMode) {
8352           case MachinePlaysBlack:
8353           case IcsPlayingBlack:
8354             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8355             break;
8356           case MachinePlaysWhite:
8357           case IcsPlayingWhite:
8358             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8359             break;
8360           case TwoMachinesPlay:
8361             if (cps->twoMachinesColor[0] == 'w')
8362               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8363             else
8364               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8365             break;
8366           default:
8367             /* can't happen */
8368             break;
8369         }
8370         return;
8371     } else if (strncmp(message, "checkmate", 9) == 0) {
8372         if (WhiteOnMove(forwardMostMove)) {
8373             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8374         } else {
8375             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8376         }
8377         return;
8378     } else if (strstr(message, "Draw") != NULL ||
8379                strstr(message, "game is a draw") != NULL) {
8380         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8381         return;
8382     } else if (strstr(message, "offer") != NULL &&
8383                strstr(message, "draw") != NULL) {
8384 #if ZIPPY
8385         if (appData.zippyPlay && first.initDone) {
8386             /* Relay offer to ICS */
8387             SendToICS(ics_prefix);
8388             SendToICS("draw\n");
8389         }
8390 #endif
8391         cps->offeredDraw = 2; /* valid until this engine moves twice */
8392         if (gameMode == TwoMachinesPlay) {
8393             if (cps->other->offeredDraw) {
8394                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8395             /* [HGM] in two-machine mode we delay relaying draw offer      */
8396             /* until after we also have move, to see if it is really claim */
8397             }
8398         } else if (gameMode == MachinePlaysWhite ||
8399                    gameMode == MachinePlaysBlack) {
8400           if (userOfferedDraw) {
8401             DisplayInformation(_("Machine accepts your draw offer"));
8402             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8403           } else {
8404             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8405           }
8406         }
8407     }
8408
8409
8410     /*
8411      * Look for thinking output
8412      */
8413     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8414           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8415                                 ) {
8416         int plylev, mvleft, mvtot, curscore, time;
8417         char mvname[MOVE_LEN];
8418         u64 nodes; // [DM]
8419         char plyext;
8420         int ignore = FALSE;
8421         int prefixHint = FALSE;
8422         mvname[0] = NULLCHAR;
8423
8424         switch (gameMode) {
8425           case MachinePlaysBlack:
8426           case IcsPlayingBlack:
8427             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8428             break;
8429           case MachinePlaysWhite:
8430           case IcsPlayingWhite:
8431             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8432             break;
8433           case AnalyzeMode:
8434           case AnalyzeFile:
8435             break;
8436           case IcsObserving: /* [DM] icsEngineAnalyze */
8437             if (!appData.icsEngineAnalyze) ignore = TRUE;
8438             break;
8439           case TwoMachinesPlay:
8440             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8441                 ignore = TRUE;
8442             }
8443             break;
8444           default:
8445             ignore = TRUE;
8446             break;
8447         }
8448
8449         if (!ignore) {
8450             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8451             buf1[0] = NULLCHAR;
8452             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8453                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8454
8455                 if (plyext != ' ' && plyext != '\t') {
8456                     time *= 100;
8457                 }
8458
8459                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8460                 if( cps->scoreIsAbsolute &&
8461                     ( gameMode == MachinePlaysBlack ||
8462                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8463                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8464                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8465                      !WhiteOnMove(currentMove)
8466                     ) )
8467                 {
8468                     curscore = -curscore;
8469                 }
8470
8471
8472                 tempStats.depth = plylev;
8473                 tempStats.nodes = nodes;
8474                 tempStats.time = time;
8475                 tempStats.score = curscore;
8476                 tempStats.got_only_move = 0;
8477
8478                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8479                         int ticklen;
8480
8481                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8482                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8483                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8484                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8485                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8486                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8487                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8488                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8489                 }
8490
8491                 /* Buffer overflow protection */
8492                 if (buf1[0] != NULLCHAR) {
8493                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8494                         && appData.debugMode) {
8495                         fprintf(debugFP,
8496                                 "PV is too long; using the first %u bytes.\n",
8497                                 (unsigned) sizeof(tempStats.movelist) - 1);
8498                     }
8499
8500                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8501                 } else {
8502                     sprintf(tempStats.movelist, " no PV\n");
8503                 }
8504
8505                 if (tempStats.seen_stat) {
8506                     tempStats.ok_to_send = 1;
8507                 }
8508
8509                 if (strchr(tempStats.movelist, '(') != NULL) {
8510                     tempStats.line_is_book = 1;
8511                     tempStats.nr_moves = 0;
8512                     tempStats.moves_left = 0;
8513                 } else {
8514                     tempStats.line_is_book = 0;
8515                 }
8516
8517                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8518                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8519
8520                 SendProgramStatsToFrontend( cps, &tempStats );
8521
8522                 /*
8523                     [AS] Protect the thinkOutput buffer from overflow... this
8524                     is only useful if buf1 hasn't overflowed first!
8525                 */
8526                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8527                          plylev,
8528                          (gameMode == TwoMachinesPlay ?
8529                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8530                          ((double) curscore) / 100.0,
8531                          prefixHint ? lastHint : "",
8532                          prefixHint ? " " : "" );
8533
8534                 if( buf1[0] != NULLCHAR ) {
8535                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8536
8537                     if( strlen(buf1) > max_len ) {
8538                         if( appData.debugMode) {
8539                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8540                         }
8541                         buf1[max_len+1] = '\0';
8542                     }
8543
8544                     strcat( thinkOutput, buf1 );
8545                 }
8546
8547                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8548                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8549                     DisplayMove(currentMove - 1);
8550                 }
8551                 return;
8552
8553             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8554                 /* crafty (9.25+) says "(only move) <move>"
8555                  * if there is only 1 legal move
8556                  */
8557                 sscanf(p, "(only move) %s", buf1);
8558                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8559                 sprintf(programStats.movelist, "%s (only move)", buf1);
8560                 programStats.depth = 1;
8561                 programStats.nr_moves = 1;
8562                 programStats.moves_left = 1;
8563                 programStats.nodes = 1;
8564                 programStats.time = 1;
8565                 programStats.got_only_move = 1;
8566
8567                 /* Not really, but we also use this member to
8568                    mean "line isn't going to change" (Crafty
8569                    isn't searching, so stats won't change) */
8570                 programStats.line_is_book = 1;
8571
8572                 SendProgramStatsToFrontend( cps, &programStats );
8573
8574                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8575                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8576                     DisplayMove(currentMove - 1);
8577                 }
8578                 return;
8579             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8580                               &time, &nodes, &plylev, &mvleft,
8581                               &mvtot, mvname) >= 5) {
8582                 /* The stat01: line is from Crafty (9.29+) in response
8583                    to the "." command */
8584                 programStats.seen_stat = 1;
8585                 cps->maybeThinking = TRUE;
8586
8587                 if (programStats.got_only_move || !appData.periodicUpdates)
8588                   return;
8589
8590                 programStats.depth = plylev;
8591                 programStats.time = time;
8592                 programStats.nodes = nodes;
8593                 programStats.moves_left = mvleft;
8594                 programStats.nr_moves = mvtot;
8595                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8596                 programStats.ok_to_send = 1;
8597                 programStats.movelist[0] = '\0';
8598
8599                 SendProgramStatsToFrontend( cps, &programStats );
8600
8601                 return;
8602
8603             } else if (strncmp(message,"++",2) == 0) {
8604                 /* Crafty 9.29+ outputs this */
8605                 programStats.got_fail = 2;
8606                 return;
8607
8608             } else if (strncmp(message,"--",2) == 0) {
8609                 /* Crafty 9.29+ outputs this */
8610                 programStats.got_fail = 1;
8611                 return;
8612
8613             } else if (thinkOutput[0] != NULLCHAR &&
8614                        strncmp(message, "    ", 4) == 0) {
8615                 unsigned message_len;
8616
8617                 p = message;
8618                 while (*p && *p == ' ') p++;
8619
8620                 message_len = strlen( p );
8621
8622                 /* [AS] Avoid buffer overflow */
8623                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8624                     strcat(thinkOutput, " ");
8625                     strcat(thinkOutput, p);
8626                 }
8627
8628                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8629                     strcat(programStats.movelist, " ");
8630                     strcat(programStats.movelist, p);
8631                 }
8632
8633                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8634                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8635                     DisplayMove(currentMove - 1);
8636                 }
8637                 return;
8638             }
8639         }
8640         else {
8641             buf1[0] = NULLCHAR;
8642
8643             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8644                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8645             {
8646                 ChessProgramStats cpstats;
8647
8648                 if (plyext != ' ' && plyext != '\t') {
8649                     time *= 100;
8650                 }
8651
8652                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8653                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8654                     curscore = -curscore;
8655                 }
8656
8657                 cpstats.depth = plylev;
8658                 cpstats.nodes = nodes;
8659                 cpstats.time = time;
8660                 cpstats.score = curscore;
8661                 cpstats.got_only_move = 0;
8662                 cpstats.movelist[0] = '\0';
8663
8664                 if (buf1[0] != NULLCHAR) {
8665                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8666                 }
8667
8668                 cpstats.ok_to_send = 0;
8669                 cpstats.line_is_book = 0;
8670                 cpstats.nr_moves = 0;
8671                 cpstats.moves_left = 0;
8672
8673                 SendProgramStatsToFrontend( cps, &cpstats );
8674             }
8675         }
8676     }
8677 }
8678
8679
8680 /* Parse a game score from the character string "game", and
8681    record it as the history of the current game.  The game
8682    score is NOT assumed to start from the standard position.
8683    The display is not updated in any way.
8684    */
8685 void
8686 ParseGameHistory(game)
8687      char *game;
8688 {
8689     ChessMove moveType;
8690     int fromX, fromY, toX, toY, boardIndex;
8691     char promoChar;
8692     char *p, *q;
8693     char buf[MSG_SIZ];
8694
8695     if (appData.debugMode)
8696       fprintf(debugFP, "Parsing game history: %s\n", game);
8697
8698     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8699     gameInfo.site = StrSave(appData.icsHost);
8700     gameInfo.date = PGNDate();
8701     gameInfo.round = StrSave("-");
8702
8703     /* Parse out names of players */
8704     while (*game == ' ') game++;
8705     p = buf;
8706     while (*game != ' ') *p++ = *game++;
8707     *p = NULLCHAR;
8708     gameInfo.white = StrSave(buf);
8709     while (*game == ' ') game++;
8710     p = buf;
8711     while (*game != ' ' && *game != '\n') *p++ = *game++;
8712     *p = NULLCHAR;
8713     gameInfo.black = StrSave(buf);
8714
8715     /* Parse moves */
8716     boardIndex = blackPlaysFirst ? 1 : 0;
8717     yynewstr(game);
8718     for (;;) {
8719         yyboardindex = boardIndex;
8720         moveType = (ChessMove) Myylex();
8721         switch (moveType) {
8722           case IllegalMove:             /* maybe suicide chess, etc. */
8723   if (appData.debugMode) {
8724     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8725     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8726     setbuf(debugFP, NULL);
8727   }
8728           case WhitePromotion:
8729           case BlackPromotion:
8730           case WhiteNonPromotion:
8731           case BlackNonPromotion:
8732           case NormalMove:
8733           case WhiteCapturesEnPassant:
8734           case BlackCapturesEnPassant:
8735           case WhiteKingSideCastle:
8736           case WhiteQueenSideCastle:
8737           case BlackKingSideCastle:
8738           case BlackQueenSideCastle:
8739           case WhiteKingSideCastleWild:
8740           case WhiteQueenSideCastleWild:
8741           case BlackKingSideCastleWild:
8742           case BlackQueenSideCastleWild:
8743           /* PUSH Fabien */
8744           case WhiteHSideCastleFR:
8745           case WhiteASideCastleFR:
8746           case BlackHSideCastleFR:
8747           case BlackASideCastleFR:
8748           /* POP Fabien */
8749             fromX = currentMoveString[0] - AAA;
8750             fromY = currentMoveString[1] - ONE;
8751             toX = currentMoveString[2] - AAA;
8752             toY = currentMoveString[3] - ONE;
8753             promoChar = currentMoveString[4];
8754             break;
8755           case WhiteDrop:
8756           case BlackDrop:
8757             fromX = moveType == WhiteDrop ?
8758               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8759             (int) CharToPiece(ToLower(currentMoveString[0]));
8760             fromY = DROP_RANK;
8761             toX = currentMoveString[2] - AAA;
8762             toY = currentMoveString[3] - ONE;
8763             promoChar = NULLCHAR;
8764             break;
8765           case AmbiguousMove:
8766             /* bug? */
8767             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8768   if (appData.debugMode) {
8769     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8770     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8771     setbuf(debugFP, NULL);
8772   }
8773             DisplayError(buf, 0);
8774             return;
8775           case ImpossibleMove:
8776             /* bug? */
8777             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8778   if (appData.debugMode) {
8779     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8780     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8781     setbuf(debugFP, NULL);
8782   }
8783             DisplayError(buf, 0);
8784             return;
8785           case EndOfFile:
8786             if (boardIndex < backwardMostMove) {
8787                 /* Oops, gap.  How did that happen? */
8788                 DisplayError(_("Gap in move list"), 0);
8789                 return;
8790             }
8791             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8792             if (boardIndex > forwardMostMove) {
8793                 forwardMostMove = boardIndex;
8794             }
8795             return;
8796           case ElapsedTime:
8797             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8798                 strcat(parseList[boardIndex-1], " ");
8799                 strcat(parseList[boardIndex-1], yy_text);
8800             }
8801             continue;
8802           case Comment:
8803           case PGNTag:
8804           case NAG:
8805           default:
8806             /* ignore */
8807             continue;
8808           case WhiteWins:
8809           case BlackWins:
8810           case GameIsDrawn:
8811           case GameUnfinished:
8812             if (gameMode == IcsExamining) {
8813                 if (boardIndex < backwardMostMove) {
8814                     /* Oops, gap.  How did that happen? */
8815                     return;
8816                 }
8817                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8818                 return;
8819             }
8820             gameInfo.result = moveType;
8821             p = strchr(yy_text, '{');
8822             if (p == NULL) p = strchr(yy_text, '(');
8823             if (p == NULL) {
8824                 p = yy_text;
8825                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8826             } else {
8827                 q = strchr(p, *p == '{' ? '}' : ')');
8828                 if (q != NULL) *q = NULLCHAR;
8829                 p++;
8830             }
8831             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8832             gameInfo.resultDetails = StrSave(p);
8833             continue;
8834         }
8835         if (boardIndex >= forwardMostMove &&
8836             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8837             backwardMostMove = blackPlaysFirst ? 1 : 0;
8838             return;
8839         }
8840         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8841                                  fromY, fromX, toY, toX, promoChar,
8842                                  parseList[boardIndex]);
8843         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8844         /* currentMoveString is set as a side-effect of yylex */
8845         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8846         strcat(moveList[boardIndex], "\n");
8847         boardIndex++;
8848         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8849         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8850           case MT_NONE:
8851           case MT_STALEMATE:
8852           default:
8853             break;
8854           case MT_CHECK:
8855             if(gameInfo.variant != VariantShogi)
8856                 strcat(parseList[boardIndex - 1], "+");
8857             break;
8858           case MT_CHECKMATE:
8859           case MT_STAINMATE:
8860             strcat(parseList[boardIndex - 1], "#");
8861             break;
8862         }
8863     }
8864 }
8865
8866
8867 /* Apply a move to the given board  */
8868 void
8869 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8870      int fromX, fromY, toX, toY;
8871      int promoChar;
8872      Board board;
8873 {
8874   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8875   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8876
8877     /* [HGM] compute & store e.p. status and castling rights for new position */
8878     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8879
8880       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8881       oldEP = (signed char)board[EP_STATUS];
8882       board[EP_STATUS] = EP_NONE;
8883
8884       if( board[toY][toX] != EmptySquare )
8885            board[EP_STATUS] = EP_CAPTURE;
8886
8887   if (fromY == DROP_RANK) {
8888         /* must be first */
8889         piece = board[toY][toX] = (ChessSquare) fromX;
8890   } else {
8891       int i;
8892
8893       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8894            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8895                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8896       } else
8897       if( board[fromY][fromX] == WhitePawn ) {
8898            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8899                board[EP_STATUS] = EP_PAWN_MOVE;
8900            if( toY-fromY==2) {
8901                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8902                         gameInfo.variant != VariantBerolina || toX < fromX)
8903                       board[EP_STATUS] = toX | berolina;
8904                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8905                         gameInfo.variant != VariantBerolina || toX > fromX)
8906                       board[EP_STATUS] = toX;
8907            }
8908       } else
8909       if( board[fromY][fromX] == BlackPawn ) {
8910            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8911                board[EP_STATUS] = EP_PAWN_MOVE;
8912            if( toY-fromY== -2) {
8913                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8914                         gameInfo.variant != VariantBerolina || toX < fromX)
8915                       board[EP_STATUS] = toX | berolina;
8916                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8917                         gameInfo.variant != VariantBerolina || toX > fromX)
8918                       board[EP_STATUS] = toX;
8919            }
8920        }
8921
8922        for(i=0; i<nrCastlingRights; i++) {
8923            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8924               board[CASTLING][i] == toX   && castlingRank[i] == toY
8925              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8926        }
8927
8928      if (fromX == toX && fromY == toY) return;
8929
8930      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8931      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8932      if(gameInfo.variant == VariantKnightmate)
8933          king += (int) WhiteUnicorn - (int) WhiteKing;
8934
8935     /* Code added by Tord: */
8936     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8937     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8938         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8939       board[fromY][fromX] = EmptySquare;
8940       board[toY][toX] = EmptySquare;
8941       if((toX > fromX) != (piece == WhiteRook)) {
8942         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8943       } else {
8944         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8945       }
8946     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8947                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8948       board[fromY][fromX] = EmptySquare;
8949       board[toY][toX] = EmptySquare;
8950       if((toX > fromX) != (piece == BlackRook)) {
8951         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8952       } else {
8953         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8954       }
8955     /* End of code added by Tord */
8956
8957     } else if (board[fromY][fromX] == king
8958         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8959         && toY == fromY && toX > fromX+1) {
8960         board[fromY][fromX] = EmptySquare;
8961         board[toY][toX] = king;
8962         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8963         board[fromY][BOARD_RGHT-1] = EmptySquare;
8964     } else if (board[fromY][fromX] == king
8965         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8966                && toY == fromY && toX < fromX-1) {
8967         board[fromY][fromX] = EmptySquare;
8968         board[toY][toX] = king;
8969         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8970         board[fromY][BOARD_LEFT] = EmptySquare;
8971     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8972                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8973                && toY >= BOARD_HEIGHT-promoRank
8974                ) {
8975         /* white pawn promotion */
8976         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8977         if (board[toY][toX] == EmptySquare) {
8978             board[toY][toX] = WhiteQueen;
8979         }
8980         if(gameInfo.variant==VariantBughouse ||
8981            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8982             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8983         board[fromY][fromX] = EmptySquare;
8984     } else if ((fromY == BOARD_HEIGHT-4)
8985                && (toX != fromX)
8986                && gameInfo.variant != VariantXiangqi
8987                && gameInfo.variant != VariantBerolina
8988                && (board[fromY][fromX] == WhitePawn)
8989                && (board[toY][toX] == EmptySquare)) {
8990         board[fromY][fromX] = EmptySquare;
8991         board[toY][toX] = WhitePawn;
8992         captured = board[toY - 1][toX];
8993         board[toY - 1][toX] = EmptySquare;
8994     } else if ((fromY == BOARD_HEIGHT-4)
8995                && (toX == fromX)
8996                && gameInfo.variant == VariantBerolina
8997                && (board[fromY][fromX] == WhitePawn)
8998                && (board[toY][toX] == EmptySquare)) {
8999         board[fromY][fromX] = EmptySquare;
9000         board[toY][toX] = WhitePawn;
9001         if(oldEP & EP_BEROLIN_A) {
9002                 captured = board[fromY][fromX-1];
9003                 board[fromY][fromX-1] = EmptySquare;
9004         }else{  captured = board[fromY][fromX+1];
9005                 board[fromY][fromX+1] = EmptySquare;
9006         }
9007     } else if (board[fromY][fromX] == king
9008         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9009                && toY == fromY && toX > fromX+1) {
9010         board[fromY][fromX] = EmptySquare;
9011         board[toY][toX] = king;
9012         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9013         board[fromY][BOARD_RGHT-1] = EmptySquare;
9014     } else if (board[fromY][fromX] == king
9015         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9016                && toY == fromY && toX < fromX-1) {
9017         board[fromY][fromX] = EmptySquare;
9018         board[toY][toX] = king;
9019         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9020         board[fromY][BOARD_LEFT] = EmptySquare;
9021     } else if (fromY == 7 && fromX == 3
9022                && board[fromY][fromX] == BlackKing
9023                && toY == 7 && toX == 5) {
9024         board[fromY][fromX] = EmptySquare;
9025         board[toY][toX] = BlackKing;
9026         board[fromY][7] = EmptySquare;
9027         board[toY][4] = BlackRook;
9028     } else if (fromY == 7 && fromX == 3
9029                && board[fromY][fromX] == BlackKing
9030                && toY == 7 && toX == 1) {
9031         board[fromY][fromX] = EmptySquare;
9032         board[toY][toX] = BlackKing;
9033         board[fromY][0] = EmptySquare;
9034         board[toY][2] = BlackRook;
9035     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9036                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9037                && toY < promoRank
9038                ) {
9039         /* black pawn promotion */
9040         board[toY][toX] = CharToPiece(ToLower(promoChar));
9041         if (board[toY][toX] == EmptySquare) {
9042             board[toY][toX] = BlackQueen;
9043         }
9044         if(gameInfo.variant==VariantBughouse ||
9045            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9046             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9047         board[fromY][fromX] = EmptySquare;
9048     } else if ((fromY == 3)
9049                && (toX != fromX)
9050                && gameInfo.variant != VariantXiangqi
9051                && gameInfo.variant != VariantBerolina
9052                && (board[fromY][fromX] == BlackPawn)
9053                && (board[toY][toX] == EmptySquare)) {
9054         board[fromY][fromX] = EmptySquare;
9055         board[toY][toX] = BlackPawn;
9056         captured = board[toY + 1][toX];
9057         board[toY + 1][toX] = EmptySquare;
9058     } else if ((fromY == 3)
9059                && (toX == fromX)
9060                && gameInfo.variant == VariantBerolina
9061                && (board[fromY][fromX] == BlackPawn)
9062                && (board[toY][toX] == EmptySquare)) {
9063         board[fromY][fromX] = EmptySquare;
9064         board[toY][toX] = BlackPawn;
9065         if(oldEP & EP_BEROLIN_A) {
9066                 captured = board[fromY][fromX-1];
9067                 board[fromY][fromX-1] = EmptySquare;
9068         }else{  captured = board[fromY][fromX+1];
9069                 board[fromY][fromX+1] = EmptySquare;
9070         }
9071     } else {
9072         board[toY][toX] = board[fromY][fromX];
9073         board[fromY][fromX] = EmptySquare;
9074     }
9075   }
9076
9077     if (gameInfo.holdingsWidth != 0) {
9078
9079       /* !!A lot more code needs to be written to support holdings  */
9080       /* [HGM] OK, so I have written it. Holdings are stored in the */
9081       /* penultimate board files, so they are automaticlly stored   */
9082       /* in the game history.                                       */
9083       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9084                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9085         /* Delete from holdings, by decreasing count */
9086         /* and erasing image if necessary            */
9087         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9088         if(p < (int) BlackPawn) { /* white drop */
9089              p -= (int)WhitePawn;
9090                  p = PieceToNumber((ChessSquare)p);
9091              if(p >= gameInfo.holdingsSize) p = 0;
9092              if(--board[p][BOARD_WIDTH-2] <= 0)
9093                   board[p][BOARD_WIDTH-1] = EmptySquare;
9094              if((int)board[p][BOARD_WIDTH-2] < 0)
9095                         board[p][BOARD_WIDTH-2] = 0;
9096         } else {                  /* black drop */
9097              p -= (int)BlackPawn;
9098                  p = PieceToNumber((ChessSquare)p);
9099              if(p >= gameInfo.holdingsSize) p = 0;
9100              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9101                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9102              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9103                         board[BOARD_HEIGHT-1-p][1] = 0;
9104         }
9105       }
9106       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9107           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9108         /* [HGM] holdings: Add to holdings, if holdings exist */
9109         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9110                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9111                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9112         }
9113         p = (int) captured;
9114         if (p >= (int) BlackPawn) {
9115           p -= (int)BlackPawn;
9116           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9117                   /* in Shogi restore piece to its original  first */
9118                   captured = (ChessSquare) (DEMOTED captured);
9119                   p = DEMOTED p;
9120           }
9121           p = PieceToNumber((ChessSquare)p);
9122           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9123           board[p][BOARD_WIDTH-2]++;
9124           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9125         } else {
9126           p -= (int)WhitePawn;
9127           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9128                   captured = (ChessSquare) (DEMOTED captured);
9129                   p = DEMOTED p;
9130           }
9131           p = PieceToNumber((ChessSquare)p);
9132           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9133           board[BOARD_HEIGHT-1-p][1]++;
9134           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9135         }
9136       }
9137     } else if (gameInfo.variant == VariantAtomic) {
9138       if (captured != EmptySquare) {
9139         int y, x;
9140         for (y = toY-1; y <= toY+1; y++) {
9141           for (x = toX-1; x <= toX+1; x++) {
9142             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9143                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9144               board[y][x] = EmptySquare;
9145             }
9146           }
9147         }
9148         board[toY][toX] = EmptySquare;
9149       }
9150     }
9151     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9152         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9153     } else
9154     if(promoChar == '+') {
9155         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9156         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9157     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9158         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9159     }
9160     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9161                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9162         // [HGM] superchess: take promotion piece out of holdings
9163         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9164         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9165             if(!--board[k][BOARD_WIDTH-2])
9166                 board[k][BOARD_WIDTH-1] = EmptySquare;
9167         } else {
9168             if(!--board[BOARD_HEIGHT-1-k][1])
9169                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9170         }
9171     }
9172
9173 }
9174
9175 /* Updates forwardMostMove */
9176 void
9177 MakeMove(fromX, fromY, toX, toY, promoChar)
9178      int fromX, fromY, toX, toY;
9179      int promoChar;
9180 {
9181 //    forwardMostMove++; // [HGM] bare: moved downstream
9182
9183     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9184         int timeLeft; static int lastLoadFlag=0; int king, piece;
9185         piece = boards[forwardMostMove][fromY][fromX];
9186         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9187         if(gameInfo.variant == VariantKnightmate)
9188             king += (int) WhiteUnicorn - (int) WhiteKing;
9189         if(forwardMostMove == 0) {
9190             if(blackPlaysFirst)
9191                 fprintf(serverMoves, "%s;", second.tidy);
9192             fprintf(serverMoves, "%s;", first.tidy);
9193             if(!blackPlaysFirst)
9194                 fprintf(serverMoves, "%s;", second.tidy);
9195         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9196         lastLoadFlag = loadFlag;
9197         // print base move
9198         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9199         // print castling suffix
9200         if( toY == fromY && piece == king ) {
9201             if(toX-fromX > 1)
9202                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9203             if(fromX-toX >1)
9204                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9205         }
9206         // e.p. suffix
9207         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9208              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9209              boards[forwardMostMove][toY][toX] == EmptySquare
9210              && fromX != toX && fromY != toY)
9211                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9212         // promotion suffix
9213         if(promoChar != NULLCHAR)
9214                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9215         if(!loadFlag) {
9216             fprintf(serverMoves, "/%d/%d",
9217                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9218             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9219             else                      timeLeft = blackTimeRemaining/1000;
9220             fprintf(serverMoves, "/%d", timeLeft);
9221         }
9222         fflush(serverMoves);
9223     }
9224
9225     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9226       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9227                         0, 1);
9228       return;
9229     }
9230     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9231     if (commentList[forwardMostMove+1] != NULL) {
9232         free(commentList[forwardMostMove+1]);
9233         commentList[forwardMostMove+1] = NULL;
9234     }
9235     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9236     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9237     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9238     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9239     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9240     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9241     gameInfo.result = GameUnfinished;
9242     if (gameInfo.resultDetails != NULL) {
9243         free(gameInfo.resultDetails);
9244         gameInfo.resultDetails = NULL;
9245     }
9246     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9247                               moveList[forwardMostMove - 1]);
9248     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9249                              PosFlags(forwardMostMove - 1),
9250                              fromY, fromX, toY, toX, promoChar,
9251                              parseList[forwardMostMove - 1]);
9252     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9253       case MT_NONE:
9254       case MT_STALEMATE:
9255       default:
9256         break;
9257       case MT_CHECK:
9258         if(gameInfo.variant != VariantShogi)
9259             strcat(parseList[forwardMostMove - 1], "+");
9260         break;
9261       case MT_CHECKMATE:
9262       case MT_STAINMATE:
9263         strcat(parseList[forwardMostMove - 1], "#");
9264         break;
9265     }
9266     if (appData.debugMode) {
9267         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9268     }
9269
9270 }
9271
9272 /* Updates currentMove if not pausing */
9273 void
9274 ShowMove(fromX, fromY, toX, toY)
9275 {
9276     int instant = (gameMode == PlayFromGameFile) ?
9277         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9278     if(appData.noGUI) return;
9279     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9280         if (!instant) {
9281             if (forwardMostMove == currentMove + 1) {
9282                 AnimateMove(boards[forwardMostMove - 1],
9283                             fromX, fromY, toX, toY);
9284             }
9285             if (appData.highlightLastMove) {
9286                 SetHighlights(fromX, fromY, toX, toY);
9287             }
9288         }
9289         currentMove = forwardMostMove;
9290     }
9291
9292     if (instant) return;
9293
9294     DisplayMove(currentMove - 1);
9295     DrawPosition(FALSE, boards[currentMove]);
9296     DisplayBothClocks();
9297     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9298     DisplayBook(currentMove);
9299 }
9300
9301 void SendEgtPath(ChessProgramState *cps)
9302 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9303         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9304
9305         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9306
9307         while(*p) {
9308             char c, *q = name+1, *r, *s;
9309
9310             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9311             while(*p && *p != ',') *q++ = *p++;
9312             *q++ = ':'; *q = 0;
9313             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9314                 strcmp(name, ",nalimov:") == 0 ) {
9315                 // take nalimov path from the menu-changeable option first, if it is defined
9316               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9317                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9318             } else
9319             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9320                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9321                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9322                 s = r = StrStr(s, ":") + 1; // beginning of path info
9323                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9324                 c = *r; *r = 0;             // temporarily null-terminate path info
9325                     *--q = 0;               // strip of trailig ':' from name
9326                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9327                 *r = c;
9328                 SendToProgram(buf,cps);     // send egtbpath command for this format
9329             }
9330             if(*p == ',') p++; // read away comma to position for next format name
9331         }
9332 }
9333
9334 void
9335 InitChessProgram(cps, setup)
9336      ChessProgramState *cps;
9337      int setup; /* [HGM] needed to setup FRC opening position */
9338 {
9339     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9340     if (appData.noChessProgram) return;
9341     hintRequested = FALSE;
9342     bookRequested = FALSE;
9343
9344     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9345     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9346     if(cps->memSize) { /* [HGM] memory */
9347       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9348         SendToProgram(buf, cps);
9349     }
9350     SendEgtPath(cps); /* [HGM] EGT */
9351     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9352       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9353         SendToProgram(buf, cps);
9354     }
9355
9356     SendToProgram(cps->initString, cps);
9357     if (gameInfo.variant != VariantNormal &&
9358         gameInfo.variant != VariantLoadable
9359         /* [HGM] also send variant if board size non-standard */
9360         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9361                                             ) {
9362       char *v = VariantName(gameInfo.variant);
9363       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9364         /* [HGM] in protocol 1 we have to assume all variants valid */
9365         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9366         DisplayFatalError(buf, 0, 1);
9367         return;
9368       }
9369
9370       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9371       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9372       if( gameInfo.variant == VariantXiangqi )
9373            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9374       if( gameInfo.variant == VariantShogi )
9375            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9376       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9377            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9378       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9379           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9380            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9381       if( gameInfo.variant == VariantCourier )
9382            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9383       if( gameInfo.variant == VariantSuper )
9384            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9385       if( gameInfo.variant == VariantGreat )
9386            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9387       if( gameInfo.variant == VariantSChess )
9388            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9389
9390       if(overruled) {
9391         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9392                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9393            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9394            if(StrStr(cps->variants, b) == NULL) {
9395                // specific sized variant not known, check if general sizing allowed
9396                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9397                    if(StrStr(cps->variants, "boardsize") == NULL) {
9398                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9399                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9400                        DisplayFatalError(buf, 0, 1);
9401                        return;
9402                    }
9403                    /* [HGM] here we really should compare with the maximum supported board size */
9404                }
9405            }
9406       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9407       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9408       SendToProgram(buf, cps);
9409     }
9410     currentlyInitializedVariant = gameInfo.variant;
9411
9412     /* [HGM] send opening position in FRC to first engine */
9413     if(setup) {
9414           SendToProgram("force\n", cps);
9415           SendBoard(cps, 0);
9416           /* engine is now in force mode! Set flag to wake it up after first move. */
9417           setboardSpoiledMachineBlack = 1;
9418     }
9419
9420     if (cps->sendICS) {
9421       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9422       SendToProgram(buf, cps);
9423     }
9424     cps->maybeThinking = FALSE;
9425     cps->offeredDraw = 0;
9426     if (!appData.icsActive) {
9427         SendTimeControl(cps, movesPerSession, timeControl,
9428                         timeIncrement, appData.searchDepth,
9429                         searchTime);
9430     }
9431     if (appData.showThinking
9432         // [HGM] thinking: four options require thinking output to be sent
9433         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9434                                 ) {
9435         SendToProgram("post\n", cps);
9436     }
9437     SendToProgram("hard\n", cps);
9438     if (!appData.ponderNextMove) {
9439         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9440            it without being sure what state we are in first.  "hard"
9441            is not a toggle, so that one is OK.
9442          */
9443         SendToProgram("easy\n", cps);
9444     }
9445     if (cps->usePing) {
9446       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9447       SendToProgram(buf, cps);
9448     }
9449     cps->initDone = TRUE;
9450 }
9451
9452
9453 void
9454 StartChessProgram(cps)
9455      ChessProgramState *cps;
9456 {
9457     char buf[MSG_SIZ];
9458     int err;
9459
9460     if (appData.noChessProgram) return;
9461     cps->initDone = FALSE;
9462
9463     if (strcmp(cps->host, "localhost") == 0) {
9464         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9465     } else if (*appData.remoteShell == NULLCHAR) {
9466         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9467     } else {
9468         if (*appData.remoteUser == NULLCHAR) {
9469           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9470                     cps->program);
9471         } else {
9472           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9473                     cps->host, appData.remoteUser, cps->program);
9474         }
9475         err = StartChildProcess(buf, "", &cps->pr);
9476     }
9477
9478     if (err != 0) {
9479       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9480         DisplayFatalError(buf, err, 1);
9481         cps->pr = NoProc;
9482         cps->isr = NULL;
9483         return;
9484     }
9485
9486     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9487     if (cps->protocolVersion > 1) {
9488       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9489       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9490       cps->comboCnt = 0;  //                and values of combo boxes
9491       SendToProgram(buf, cps);
9492     } else {
9493       SendToProgram("xboard\n", cps);
9494     }
9495 }
9496
9497 void
9498 TwoMachinesEventIfReady P((void))
9499 {
9500   static int curMess = 0;
9501   if (first.lastPing != first.lastPong) {
9502     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9503     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9504     return;
9505   }
9506   if (second.lastPing != second.lastPong) {
9507     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9508     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9509     return;
9510   }
9511   DisplayMessage("", ""); curMess = 0;
9512   ThawUI();
9513   TwoMachinesEvent();
9514 }
9515
9516 int
9517 CreateTourney(char *name)
9518 {
9519         FILE *f;
9520         if(name[0] == NULLCHAR) return 0;
9521         f = fopen(appData.tourneyFile, "r");
9522         if(f) { // file exists
9523             ParseArgsFromFile(f); // parse it
9524         } else {
9525             f = fopen(appData.tourneyFile, "w");
9526             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9527                 // create a file with tournament description
9528                 fprintf(f, "-participants {%s}\n", appData.participants);
9529                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9530                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9531                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9532                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9533                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9534                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9535                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9536                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9537                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9538                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9539                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9540                 if(searchTime > 0)
9541                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9542                 else {
9543                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9544                         fprintf(f, "-tc %s\n", appData.timeControl);
9545                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9546                 }
9547                 fprintf(f, "-results \"\"\n");
9548             }
9549         }
9550         fclose(f);
9551         appData.noChessProgram = FALSE;
9552         appData.clockMode = TRUE;
9553         SetGNUMode();
9554         return 1;
9555 }
9556
9557 #define MAXENGINES 1000
9558 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9559
9560 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9561 {
9562     char buf[MSG_SIZ], *p, *q;
9563     int i=1;
9564     while(*names) {
9565         p = names; q = buf;
9566         while(*p && *p != '\n') *q++ = *p++;
9567         *q = 0;
9568         if(engineList[i]) free(engineList[i]);
9569         engineList[i] = strdup(buf);
9570         if(*p == '\n') p++;
9571         TidyProgramName(engineList[i], "localhost", buf);
9572         if(engineMnemonic[i]) free(engineMnemonic[i]);
9573         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9574             strcat(buf, " (");
9575             sscanf(q + 8, "%s", buf + strlen(buf));
9576             strcat(buf, ")");
9577         }
9578         engineMnemonic[i] = strdup(buf);
9579         names = p; i++;
9580       if(i > MAXENGINES - 2) break;
9581     }
9582     engineList[i] = NULL;
9583 }
9584
9585 // following implemented as macro to avoid type limitations
9586 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9587
9588 void SwapEngines(int n)
9589 {   // swap settings for first engine and other engine (so far only some selected options)
9590     int h;
9591     char *p;
9592     if(n == 0) return;
9593     SWAP(directory, p)
9594     SWAP(chessProgram, p)
9595     SWAP(isUCI, h)
9596     SWAP(hasOwnBookUCI, h)
9597     SWAP(protocolVersion, h)
9598     SWAP(reuse, h)
9599     SWAP(scoreIsAbsolute, h)
9600     SWAP(timeOdds, h)
9601     SWAP(logo, p)
9602     SWAP(pgnName, p)
9603 }
9604
9605 void
9606 SetPlayer(int player)
9607 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9608     int i;
9609     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9610     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9611     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9612     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9613     if(mnemonic[i]) {
9614         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9615         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9616         ParseArgsFromString(buf);
9617     }
9618     free(engineName);
9619 }
9620
9621 int
9622 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9623 {   // determine players from game number
9624     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9625
9626     if(appData.tourneyType == 0) {
9627         roundsPerCycle = (nPlayers - 1) | 1;
9628         pairingsPerRound = nPlayers / 2;
9629     } else if(appData.tourneyType > 0) {
9630         roundsPerCycle = nPlayers - appData.tourneyType;
9631         pairingsPerRound = appData.tourneyType;
9632     }
9633     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9634     gamesPerCycle = gamesPerRound * roundsPerCycle;
9635     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9636     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9637     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9638     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9639     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9640     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9641
9642     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9643     if(appData.roundSync) *syncInterval = gamesPerRound;
9644
9645     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9646
9647     if(appData.tourneyType == 0) {
9648         if(curPairing == (nPlayers-1)/2 ) {
9649             *whitePlayer = curRound;
9650             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9651         } else {
9652             *whitePlayer = curRound - pairingsPerRound + curPairing;
9653             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9654             *blackPlayer = curRound + pairingsPerRound - curPairing;
9655             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9656         }
9657     } else if(appData.tourneyType > 0) {
9658         *whitePlayer = curPairing;
9659         *blackPlayer = curRound + appData.tourneyType;
9660     }
9661
9662     // take care of white/black alternation per round. 
9663     // For cycles and games this is already taken care of by default, derived from matchGame!
9664     return curRound & 1;
9665 }
9666
9667 int
9668 NextTourneyGame(int nr, int *swapColors)
9669 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9670     char *p, *q;
9671     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9672     FILE *tf;
9673     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9674     tf = fopen(appData.tourneyFile, "r");
9675     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9676     ParseArgsFromFile(tf); fclose(tf);
9677     InitTimeControls(); // TC might be altered from tourney file
9678
9679     p = appData.participants;
9680     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9681     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9682
9683     if(syncInterval) {
9684         p = q = appData.results;
9685         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9686         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9687             DisplayMessage(_("Waiting for other game(s)"),"");
9688             waitingForGame = TRUE;
9689             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9690             return 0;
9691         }
9692         waitingForGame = FALSE;
9693     }
9694
9695     if(first.pr != NoProc) return 1; // engines already loaded
9696
9697     // redefine engines, engine dir, etc.
9698     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9699     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9700     SwapEngines(1);
9701     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9702     SwapEngines(1);         // and make that valid for second engine by swapping
9703     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9704     InitEngine(&second, 1);
9705     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9706     return 1;
9707 }
9708
9709 void
9710 NextMatchGame()
9711 {   // performs game initialization that does not invoke engines, and then tries to start the game
9712     int firstWhite, swapColors = 0;
9713     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9714     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9715     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9716     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9717     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9718     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9719     Reset(FALSE, first.pr != NoProc);
9720     appData.noChessProgram = FALSE;
9721     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9722     TwoMachinesEvent();
9723 }
9724
9725 void UserAdjudicationEvent( int result )
9726 {
9727     ChessMove gameResult = GameIsDrawn;
9728
9729     if( result > 0 ) {
9730         gameResult = WhiteWins;
9731     }
9732     else if( result < 0 ) {
9733         gameResult = BlackWins;
9734     }
9735
9736     if( gameMode == TwoMachinesPlay ) {
9737         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9738     }
9739 }
9740
9741
9742 // [HGM] save: calculate checksum of game to make games easily identifiable
9743 int StringCheckSum(char *s)
9744 {
9745         int i = 0;
9746         if(s==NULL) return 0;
9747         while(*s) i = i*259 + *s++;
9748         return i;
9749 }
9750
9751 int GameCheckSum()
9752 {
9753         int i, sum=0;
9754         for(i=backwardMostMove; i<forwardMostMove; i++) {
9755                 sum += pvInfoList[i].depth;
9756                 sum += StringCheckSum(parseList[i]);
9757                 sum += StringCheckSum(commentList[i]);
9758                 sum *= 261;
9759         }
9760         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9761         return sum + StringCheckSum(commentList[i]);
9762 } // end of save patch
9763
9764 void
9765 GameEnds(result, resultDetails, whosays)
9766      ChessMove result;
9767      char *resultDetails;
9768      int whosays;
9769 {
9770     GameMode nextGameMode;
9771     int isIcsGame;
9772     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9773
9774     if(endingGame) return; /* [HGM] crash: forbid recursion */
9775     endingGame = 1;
9776     if(twoBoards) { // [HGM] dual: switch back to one board
9777         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9778         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9779     }
9780     if (appData.debugMode) {
9781       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9782               result, resultDetails ? resultDetails : "(null)", whosays);
9783     }
9784
9785     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9786
9787     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9788         /* If we are playing on ICS, the server decides when the
9789            game is over, but the engine can offer to draw, claim
9790            a draw, or resign.
9791          */
9792 #if ZIPPY
9793         if (appData.zippyPlay && first.initDone) {
9794             if (result == GameIsDrawn) {
9795                 /* In case draw still needs to be claimed */
9796                 SendToICS(ics_prefix);
9797                 SendToICS("draw\n");
9798             } else if (StrCaseStr(resultDetails, "resign")) {
9799                 SendToICS(ics_prefix);
9800                 SendToICS("resign\n");
9801             }
9802         }
9803 #endif
9804         endingGame = 0; /* [HGM] crash */
9805         return;
9806     }
9807
9808     /* If we're loading the game from a file, stop */
9809     if (whosays == GE_FILE) {
9810       (void) StopLoadGameTimer();
9811       gameFileFP = NULL;
9812     }
9813
9814     /* Cancel draw offers */
9815     first.offeredDraw = second.offeredDraw = 0;
9816
9817     /* If this is an ICS game, only ICS can really say it's done;
9818        if not, anyone can. */
9819     isIcsGame = (gameMode == IcsPlayingWhite ||
9820                  gameMode == IcsPlayingBlack ||
9821                  gameMode == IcsObserving    ||
9822                  gameMode == IcsExamining);
9823
9824     if (!isIcsGame || whosays == GE_ICS) {
9825         /* OK -- not an ICS game, or ICS said it was done */
9826         StopClocks();
9827         if (!isIcsGame && !appData.noChessProgram)
9828           SetUserThinkingEnables();
9829
9830         /* [HGM] if a machine claims the game end we verify this claim */
9831         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9832             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9833                 char claimer;
9834                 ChessMove trueResult = (ChessMove) -1;
9835
9836                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9837                                             first.twoMachinesColor[0] :
9838                                             second.twoMachinesColor[0] ;
9839
9840                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9841                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9842                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9843                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9844                 } else
9845                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9846                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9847                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9848                 } else
9849                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9850                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9851                 }
9852
9853                 // now verify win claims, but not in drop games, as we don't understand those yet
9854                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9855                                                  || gameInfo.variant == VariantGreat) &&
9856                     (result == WhiteWins && claimer == 'w' ||
9857                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9858                       if (appData.debugMode) {
9859                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9860                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9861                       }
9862                       if(result != trueResult) {
9863                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9864                               result = claimer == 'w' ? BlackWins : WhiteWins;
9865                               resultDetails = buf;
9866                       }
9867                 } else
9868                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9869                     && (forwardMostMove <= backwardMostMove ||
9870                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9871                         (claimer=='b')==(forwardMostMove&1))
9872                                                                                   ) {
9873                       /* [HGM] verify: draws that were not flagged are false claims */
9874                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9875                       result = claimer == 'w' ? BlackWins : WhiteWins;
9876                       resultDetails = buf;
9877                 }
9878                 /* (Claiming a loss is accepted no questions asked!) */
9879             }
9880             /* [HGM] bare: don't allow bare King to win */
9881             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9882                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9883                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9884                && result != GameIsDrawn)
9885             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9886                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9887                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9888                         if(p >= 0 && p <= (int)WhiteKing) k++;
9889                 }
9890                 if (appData.debugMode) {
9891                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9892                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9893                 }
9894                 if(k <= 1) {
9895                         result = GameIsDrawn;
9896                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9897                         resultDetails = buf;
9898                 }
9899             }
9900         }
9901
9902
9903         if(serverMoves != NULL && !loadFlag) { char c = '=';
9904             if(result==WhiteWins) c = '+';
9905             if(result==BlackWins) c = '-';
9906             if(resultDetails != NULL)
9907                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9908         }
9909         if (resultDetails != NULL) {
9910             gameInfo.result = result;
9911             gameInfo.resultDetails = StrSave(resultDetails);
9912
9913             /* display last move only if game was not loaded from file */
9914             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9915                 DisplayMove(currentMove - 1);
9916
9917             if (forwardMostMove != 0) {
9918                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9919                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9920                                                                 ) {
9921                     if (*appData.saveGameFile != NULLCHAR) {
9922                         SaveGameToFile(appData.saveGameFile, TRUE);
9923                     } else if (appData.autoSaveGames) {
9924                         AutoSaveGame();
9925                     }
9926                     if (*appData.savePositionFile != NULLCHAR) {
9927                         SavePositionToFile(appData.savePositionFile);
9928                     }
9929                 }
9930             }
9931
9932             /* Tell program how game ended in case it is learning */
9933             /* [HGM] Moved this to after saving the PGN, just in case */
9934             /* engine died and we got here through time loss. In that */
9935             /* case we will get a fatal error writing the pipe, which */
9936             /* would otherwise lose us the PGN.                       */
9937             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9938             /* output during GameEnds should never be fatal anymore   */
9939             if (gameMode == MachinePlaysWhite ||
9940                 gameMode == MachinePlaysBlack ||
9941                 gameMode == TwoMachinesPlay ||
9942                 gameMode == IcsPlayingWhite ||
9943                 gameMode == IcsPlayingBlack ||
9944                 gameMode == BeginningOfGame) {
9945                 char buf[MSG_SIZ];
9946                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9947                         resultDetails);
9948                 if (first.pr != NoProc) {
9949                     SendToProgram(buf, &first);
9950                 }
9951                 if (second.pr != NoProc &&
9952                     gameMode == TwoMachinesPlay) {
9953                     SendToProgram(buf, &second);
9954                 }
9955             }
9956         }
9957
9958         if (appData.icsActive) {
9959             if (appData.quietPlay &&
9960                 (gameMode == IcsPlayingWhite ||
9961                  gameMode == IcsPlayingBlack)) {
9962                 SendToICS(ics_prefix);
9963                 SendToICS("set shout 1\n");
9964             }
9965             nextGameMode = IcsIdle;
9966             ics_user_moved = FALSE;
9967             /* clean up premove.  It's ugly when the game has ended and the
9968              * premove highlights are still on the board.
9969              */
9970             if (gotPremove) {
9971               gotPremove = FALSE;
9972               ClearPremoveHighlights();
9973               DrawPosition(FALSE, boards[currentMove]);
9974             }
9975             if (whosays == GE_ICS) {
9976                 switch (result) {
9977                 case WhiteWins:
9978                     if (gameMode == IcsPlayingWhite)
9979                         PlayIcsWinSound();
9980                     else if(gameMode == IcsPlayingBlack)
9981                         PlayIcsLossSound();
9982                     break;
9983                 case BlackWins:
9984                     if (gameMode == IcsPlayingBlack)
9985                         PlayIcsWinSound();
9986                     else if(gameMode == IcsPlayingWhite)
9987                         PlayIcsLossSound();
9988                     break;
9989                 case GameIsDrawn:
9990                     PlayIcsDrawSound();
9991                     break;
9992                 default:
9993                     PlayIcsUnfinishedSound();
9994                 }
9995             }
9996         } else if (gameMode == EditGame ||
9997                    gameMode == PlayFromGameFile ||
9998                    gameMode == AnalyzeMode ||
9999                    gameMode == AnalyzeFile) {
10000             nextGameMode = gameMode;
10001         } else {
10002             nextGameMode = EndOfGame;
10003         }
10004         pausing = FALSE;
10005         ModeHighlight();
10006     } else {
10007         nextGameMode = gameMode;
10008     }
10009
10010     if (appData.noChessProgram) {
10011         gameMode = nextGameMode;
10012         ModeHighlight();
10013         endingGame = 0; /* [HGM] crash */
10014         return;
10015     }
10016
10017     if (first.reuse) {
10018         /* Put first chess program into idle state */
10019         if (first.pr != NoProc &&
10020             (gameMode == MachinePlaysWhite ||
10021              gameMode == MachinePlaysBlack ||
10022              gameMode == TwoMachinesPlay ||
10023              gameMode == IcsPlayingWhite ||
10024              gameMode == IcsPlayingBlack ||
10025              gameMode == BeginningOfGame)) {
10026             SendToProgram("force\n", &first);
10027             if (first.usePing) {
10028               char buf[MSG_SIZ];
10029               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10030               SendToProgram(buf, &first);
10031             }
10032         }
10033     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10034         /* Kill off first chess program */
10035         if (first.isr != NULL)
10036           RemoveInputSource(first.isr);
10037         first.isr = NULL;
10038
10039         if (first.pr != NoProc) {
10040             ExitAnalyzeMode();
10041             DoSleep( appData.delayBeforeQuit );
10042             SendToProgram("quit\n", &first);
10043             DoSleep( appData.delayAfterQuit );
10044             DestroyChildProcess(first.pr, first.useSigterm);
10045         }
10046         first.pr = NoProc;
10047     }
10048     if (second.reuse) {
10049         /* Put second chess program into idle state */
10050         if (second.pr != NoProc &&
10051             gameMode == TwoMachinesPlay) {
10052             SendToProgram("force\n", &second);
10053             if (second.usePing) {
10054               char buf[MSG_SIZ];
10055               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10056               SendToProgram(buf, &second);
10057             }
10058         }
10059     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10060         /* Kill off second chess program */
10061         if (second.isr != NULL)
10062           RemoveInputSource(second.isr);
10063         second.isr = NULL;
10064
10065         if (second.pr != NoProc) {
10066             DoSleep( appData.delayBeforeQuit );
10067             SendToProgram("quit\n", &second);
10068             DoSleep( appData.delayAfterQuit );
10069             DestroyChildProcess(second.pr, second.useSigterm);
10070         }
10071         second.pr = NoProc;
10072     }
10073
10074     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10075         char resChar = '=';
10076         switch (result) {
10077         case WhiteWins:
10078           resChar = '+';
10079           if (first.twoMachinesColor[0] == 'w') {
10080             first.matchWins++;
10081           } else {
10082             second.matchWins++;
10083           }
10084           break;
10085         case BlackWins:
10086           resChar = '-';
10087           if (first.twoMachinesColor[0] == 'b') {
10088             first.matchWins++;
10089           } else {
10090             second.matchWins++;
10091           }
10092           break;
10093         case GameUnfinished:
10094           resChar = ' ';
10095         default:
10096           break;
10097         }
10098
10099         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10100         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10101             ReserveGame(nextGame, resChar); // sets nextGame
10102             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10103         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10104
10105         if (nextGame <= appData.matchGames && !abortMatch) {
10106             gameMode = nextGameMode;
10107             matchGame = nextGame; // this will be overruled in tourney mode!
10108             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10109             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10110             endingGame = 0; /* [HGM] crash */
10111             return;
10112         } else {
10113             gameMode = nextGameMode;
10114             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10115                      first.tidy, second.tidy,
10116                      first.matchWins, second.matchWins,
10117                      appData.matchGames - (first.matchWins + second.matchWins));
10118             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10119             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10120                 first.twoMachinesColor = "black\n";
10121                 second.twoMachinesColor = "white\n";
10122             } else {
10123                 first.twoMachinesColor = "white\n";
10124                 second.twoMachinesColor = "black\n";
10125             }
10126         }
10127     }
10128     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10129         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10130       ExitAnalyzeMode();
10131     gameMode = nextGameMode;
10132     ModeHighlight();
10133     endingGame = 0;  /* [HGM] crash */
10134     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10135         if(matchMode == TRUE) { // match through command line: exit with or without popup
10136             if(ranking) {
10137                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10138                 else ExitEvent(0);
10139             } else DisplayFatalError(buf, 0, 0);
10140         } else { // match through menu; just stop, with or without popup
10141             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10142             if(ranking){
10143                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10144             } else DisplayNote(buf);
10145       }
10146       if(ranking) free(ranking);
10147     }
10148 }
10149
10150 /* Assumes program was just initialized (initString sent).
10151    Leaves program in force mode. */
10152 void
10153 FeedMovesToProgram(cps, upto)
10154      ChessProgramState *cps;
10155      int upto;
10156 {
10157     int i;
10158
10159     if (appData.debugMode)
10160       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10161               startedFromSetupPosition ? "position and " : "",
10162               backwardMostMove, upto, cps->which);
10163     if(currentlyInitializedVariant != gameInfo.variant) {
10164       char buf[MSG_SIZ];
10165         // [HGM] variantswitch: make engine aware of new variant
10166         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10167                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10168         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10169         SendToProgram(buf, cps);
10170         currentlyInitializedVariant = gameInfo.variant;
10171     }
10172     SendToProgram("force\n", cps);
10173     if (startedFromSetupPosition) {
10174         SendBoard(cps, backwardMostMove);
10175     if (appData.debugMode) {
10176         fprintf(debugFP, "feedMoves\n");
10177     }
10178     }
10179     for (i = backwardMostMove; i < upto; i++) {
10180         SendMoveToProgram(i, cps);
10181     }
10182 }
10183
10184
10185 int
10186 ResurrectChessProgram()
10187 {
10188      /* The chess program may have exited.
10189         If so, restart it and feed it all the moves made so far. */
10190     static int doInit = 0;
10191
10192     if (appData.noChessProgram) return 1;
10193
10194     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10195         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10196         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10197         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10198     } else {
10199         if (first.pr != NoProc) return 1;
10200         StartChessProgram(&first);
10201     }
10202     InitChessProgram(&first, FALSE);
10203     FeedMovesToProgram(&first, currentMove);
10204
10205     if (!first.sendTime) {
10206         /* can't tell gnuchess what its clock should read,
10207            so we bow to its notion. */
10208         ResetClocks();
10209         timeRemaining[0][currentMove] = whiteTimeRemaining;
10210         timeRemaining[1][currentMove] = blackTimeRemaining;
10211     }
10212
10213     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10214                 appData.icsEngineAnalyze) && first.analysisSupport) {
10215       SendToProgram("analyze\n", &first);
10216       first.analyzing = TRUE;
10217     }
10218     return 1;
10219 }
10220
10221 /*
10222  * Button procedures
10223  */
10224 void
10225 Reset(redraw, init)
10226      int redraw, init;
10227 {
10228     int i;
10229
10230     if (appData.debugMode) {
10231         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10232                 redraw, init, gameMode);
10233     }
10234     CleanupTail(); // [HGM] vari: delete any stored variations
10235     pausing = pauseExamInvalid = FALSE;
10236     startedFromSetupPosition = blackPlaysFirst = FALSE;
10237     firstMove = TRUE;
10238     whiteFlag = blackFlag = FALSE;
10239     userOfferedDraw = FALSE;
10240     hintRequested = bookRequested = FALSE;
10241     first.maybeThinking = FALSE;
10242     second.maybeThinking = FALSE;
10243     first.bookSuspend = FALSE; // [HGM] book
10244     second.bookSuspend = FALSE;
10245     thinkOutput[0] = NULLCHAR;
10246     lastHint[0] = NULLCHAR;
10247     ClearGameInfo(&gameInfo);
10248     gameInfo.variant = StringToVariant(appData.variant);
10249     ics_user_moved = ics_clock_paused = FALSE;
10250     ics_getting_history = H_FALSE;
10251     ics_gamenum = -1;
10252     white_holding[0] = black_holding[0] = NULLCHAR;
10253     ClearProgramStats();
10254     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10255
10256     ResetFrontEnd();
10257     ClearHighlights();
10258     flipView = appData.flipView;
10259     ClearPremoveHighlights();
10260     gotPremove = FALSE;
10261     alarmSounded = FALSE;
10262
10263     GameEnds(EndOfFile, NULL, GE_PLAYER);
10264     if(appData.serverMovesName != NULL) {
10265         /* [HGM] prepare to make moves file for broadcasting */
10266         clock_t t = clock();
10267         if(serverMoves != NULL) fclose(serverMoves);
10268         serverMoves = fopen(appData.serverMovesName, "r");
10269         if(serverMoves != NULL) {
10270             fclose(serverMoves);
10271             /* delay 15 sec before overwriting, so all clients can see end */
10272             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10273         }
10274         serverMoves = fopen(appData.serverMovesName, "w");
10275     }
10276
10277     ExitAnalyzeMode();
10278     gameMode = BeginningOfGame;
10279     ModeHighlight();
10280     if(appData.icsActive) gameInfo.variant = VariantNormal;
10281     currentMove = forwardMostMove = backwardMostMove = 0;
10282     InitPosition(redraw);
10283     for (i = 0; i < MAX_MOVES; i++) {
10284         if (commentList[i] != NULL) {
10285             free(commentList[i]);
10286             commentList[i] = NULL;
10287         }
10288     }
10289     ResetClocks();
10290     timeRemaining[0][0] = whiteTimeRemaining;
10291     timeRemaining[1][0] = blackTimeRemaining;
10292
10293     if (first.pr == NULL) {
10294         StartChessProgram(&first);
10295     }
10296     if (init) {
10297             InitChessProgram(&first, startedFromSetupPosition);
10298     }
10299     DisplayTitle("");
10300     DisplayMessage("", "");
10301     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10302     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10303 }
10304
10305 void
10306 AutoPlayGameLoop()
10307 {
10308     for (;;) {
10309         if (!AutoPlayOneMove())
10310           return;
10311         if (matchMode || appData.timeDelay == 0)
10312           continue;
10313         if (appData.timeDelay < 0)
10314           return;
10315         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10316         break;
10317     }
10318 }
10319
10320
10321 int
10322 AutoPlayOneMove()
10323 {
10324     int fromX, fromY, toX, toY;
10325
10326     if (appData.debugMode) {
10327       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10328     }
10329
10330     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10331       return FALSE;
10332
10333     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10334       pvInfoList[currentMove].depth = programStats.depth;
10335       pvInfoList[currentMove].score = programStats.score;
10336       pvInfoList[currentMove].time  = 0;
10337       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10338     }
10339
10340     if (currentMove >= forwardMostMove) {
10341       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10342       gameMode = EditGame;
10343       ModeHighlight();
10344
10345       /* [AS] Clear current move marker at the end of a game */
10346       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10347
10348       return FALSE;
10349     }
10350
10351     toX = moveList[currentMove][2] - AAA;
10352     toY = moveList[currentMove][3] - ONE;
10353
10354     if (moveList[currentMove][1] == '@') {
10355         if (appData.highlightLastMove) {
10356             SetHighlights(-1, -1, toX, toY);
10357         }
10358     } else {
10359         fromX = moveList[currentMove][0] - AAA;
10360         fromY = moveList[currentMove][1] - ONE;
10361
10362         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10363
10364         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10365
10366         if (appData.highlightLastMove) {
10367             SetHighlights(fromX, fromY, toX, toY);
10368         }
10369     }
10370     DisplayMove(currentMove);
10371     SendMoveToProgram(currentMove++, &first);
10372     DisplayBothClocks();
10373     DrawPosition(FALSE, boards[currentMove]);
10374     // [HGM] PV info: always display, routine tests if empty
10375     DisplayComment(currentMove - 1, commentList[currentMove]);
10376     return TRUE;
10377 }
10378
10379
10380 int
10381 LoadGameOneMove(readAhead)
10382      ChessMove readAhead;
10383 {
10384     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10385     char promoChar = NULLCHAR;
10386     ChessMove moveType;
10387     char move[MSG_SIZ];
10388     char *p, *q;
10389
10390     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10391         gameMode != AnalyzeMode && gameMode != Training) {
10392         gameFileFP = NULL;
10393         return FALSE;
10394     }
10395
10396     yyboardindex = forwardMostMove;
10397     if (readAhead != EndOfFile) {
10398       moveType = readAhead;
10399     } else {
10400       if (gameFileFP == NULL)
10401           return FALSE;
10402       moveType = (ChessMove) Myylex();
10403     }
10404
10405     done = FALSE;
10406     switch (moveType) {
10407       case Comment:
10408         if (appData.debugMode)
10409           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10410         p = yy_text;
10411
10412         /* append the comment but don't display it */
10413         AppendComment(currentMove, p, FALSE);
10414         return TRUE;
10415
10416       case WhiteCapturesEnPassant:
10417       case BlackCapturesEnPassant:
10418       case WhitePromotion:
10419       case BlackPromotion:
10420       case WhiteNonPromotion:
10421       case BlackNonPromotion:
10422       case NormalMove:
10423       case WhiteKingSideCastle:
10424       case WhiteQueenSideCastle:
10425       case BlackKingSideCastle:
10426       case BlackQueenSideCastle:
10427       case WhiteKingSideCastleWild:
10428       case WhiteQueenSideCastleWild:
10429       case BlackKingSideCastleWild:
10430       case BlackQueenSideCastleWild:
10431       /* PUSH Fabien */
10432       case WhiteHSideCastleFR:
10433       case WhiteASideCastleFR:
10434       case BlackHSideCastleFR:
10435       case BlackASideCastleFR:
10436       /* POP Fabien */
10437         if (appData.debugMode)
10438           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10439         fromX = currentMoveString[0] - AAA;
10440         fromY = currentMoveString[1] - ONE;
10441         toX = currentMoveString[2] - AAA;
10442         toY = currentMoveString[3] - ONE;
10443         promoChar = currentMoveString[4];
10444         break;
10445
10446       case WhiteDrop:
10447       case BlackDrop:
10448         if (appData.debugMode)
10449           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10450         fromX = moveType == WhiteDrop ?
10451           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10452         (int) CharToPiece(ToLower(currentMoveString[0]));
10453         fromY = DROP_RANK;
10454         toX = currentMoveString[2] - AAA;
10455         toY = currentMoveString[3] - ONE;
10456         break;
10457
10458       case WhiteWins:
10459       case BlackWins:
10460       case GameIsDrawn:
10461       case GameUnfinished:
10462         if (appData.debugMode)
10463           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10464         p = strchr(yy_text, '{');
10465         if (p == NULL) p = strchr(yy_text, '(');
10466         if (p == NULL) {
10467             p = yy_text;
10468             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10469         } else {
10470             q = strchr(p, *p == '{' ? '}' : ')');
10471             if (q != NULL) *q = NULLCHAR;
10472             p++;
10473         }
10474         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10475         GameEnds(moveType, p, GE_FILE);
10476         done = TRUE;
10477         if (cmailMsgLoaded) {
10478             ClearHighlights();
10479             flipView = WhiteOnMove(currentMove);
10480             if (moveType == GameUnfinished) flipView = !flipView;
10481             if (appData.debugMode)
10482               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10483         }
10484         break;
10485
10486       case EndOfFile:
10487         if (appData.debugMode)
10488           fprintf(debugFP, "Parser hit end of file\n");
10489         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10490           case MT_NONE:
10491           case MT_CHECK:
10492             break;
10493           case MT_CHECKMATE:
10494           case MT_STAINMATE:
10495             if (WhiteOnMove(currentMove)) {
10496                 GameEnds(BlackWins, "Black mates", GE_FILE);
10497             } else {
10498                 GameEnds(WhiteWins, "White mates", GE_FILE);
10499             }
10500             break;
10501           case MT_STALEMATE:
10502             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10503             break;
10504         }
10505         done = TRUE;
10506         break;
10507
10508       case MoveNumberOne:
10509         if (lastLoadGameStart == GNUChessGame) {
10510             /* GNUChessGames have numbers, but they aren't move numbers */
10511             if (appData.debugMode)
10512               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10513                       yy_text, (int) moveType);
10514             return LoadGameOneMove(EndOfFile); /* tail recursion */
10515         }
10516         /* else fall thru */
10517
10518       case XBoardGame:
10519       case GNUChessGame:
10520       case PGNTag:
10521         /* Reached start of next game in file */
10522         if (appData.debugMode)
10523           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10524         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10525           case MT_NONE:
10526           case MT_CHECK:
10527             break;
10528           case MT_CHECKMATE:
10529           case MT_STAINMATE:
10530             if (WhiteOnMove(currentMove)) {
10531                 GameEnds(BlackWins, "Black mates", GE_FILE);
10532             } else {
10533                 GameEnds(WhiteWins, "White mates", GE_FILE);
10534             }
10535             break;
10536           case MT_STALEMATE:
10537             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10538             break;
10539         }
10540         done = TRUE;
10541         break;
10542
10543       case PositionDiagram:     /* should not happen; ignore */
10544       case ElapsedTime:         /* ignore */
10545       case NAG:                 /* ignore */
10546         if (appData.debugMode)
10547           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10548                   yy_text, (int) moveType);
10549         return LoadGameOneMove(EndOfFile); /* tail recursion */
10550
10551       case IllegalMove:
10552         if (appData.testLegality) {
10553             if (appData.debugMode)
10554               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10555             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10556                     (forwardMostMove / 2) + 1,
10557                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10558             DisplayError(move, 0);
10559             done = TRUE;
10560         } else {
10561             if (appData.debugMode)
10562               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10563                       yy_text, currentMoveString);
10564             fromX = currentMoveString[0] - AAA;
10565             fromY = currentMoveString[1] - ONE;
10566             toX = currentMoveString[2] - AAA;
10567             toY = currentMoveString[3] - ONE;
10568             promoChar = currentMoveString[4];
10569         }
10570         break;
10571
10572       case AmbiguousMove:
10573         if (appData.debugMode)
10574           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10575         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10576                 (forwardMostMove / 2) + 1,
10577                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10578         DisplayError(move, 0);
10579         done = TRUE;
10580         break;
10581
10582       default:
10583       case ImpossibleMove:
10584         if (appData.debugMode)
10585           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10586         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10587                 (forwardMostMove / 2) + 1,
10588                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10589         DisplayError(move, 0);
10590         done = TRUE;
10591         break;
10592     }
10593
10594     if (done) {
10595         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10596             DrawPosition(FALSE, boards[currentMove]);
10597             DisplayBothClocks();
10598             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10599               DisplayComment(currentMove - 1, commentList[currentMove]);
10600         }
10601         (void) StopLoadGameTimer();
10602         gameFileFP = NULL;
10603         cmailOldMove = forwardMostMove;
10604         return FALSE;
10605     } else {
10606         /* currentMoveString is set as a side-effect of yylex */
10607
10608         thinkOutput[0] = NULLCHAR;
10609         MakeMove(fromX, fromY, toX, toY, promoChar);
10610         currentMove = forwardMostMove;
10611         return TRUE;
10612     }
10613 }
10614
10615 /* Load the nth game from the given file */
10616 int
10617 LoadGameFromFile(filename, n, title, useList)
10618      char *filename;
10619      int n;
10620      char *title;
10621      /*Boolean*/ int useList;
10622 {
10623     FILE *f;
10624     char buf[MSG_SIZ];
10625
10626     if (strcmp(filename, "-") == 0) {
10627         f = stdin;
10628         title = "stdin";
10629     } else {
10630         f = fopen(filename, "rb");
10631         if (f == NULL) {
10632           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10633             DisplayError(buf, errno);
10634             return FALSE;
10635         }
10636     }
10637     if (fseek(f, 0, 0) == -1) {
10638         /* f is not seekable; probably a pipe */
10639         useList = FALSE;
10640     }
10641     if (useList && n == 0) {
10642         int error = GameListBuild(f);
10643         if (error) {
10644             DisplayError(_("Cannot build game list"), error);
10645         } else if (!ListEmpty(&gameList) &&
10646                    ((ListGame *) gameList.tailPred)->number > 1) {
10647             GameListPopUp(f, title);
10648             return TRUE;
10649         }
10650         GameListDestroy();
10651         n = 1;
10652     }
10653     if (n == 0) n = 1;
10654     return LoadGame(f, n, title, FALSE);
10655 }
10656
10657
10658 void
10659 MakeRegisteredMove()
10660 {
10661     int fromX, fromY, toX, toY;
10662     char promoChar;
10663     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10664         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10665           case CMAIL_MOVE:
10666           case CMAIL_DRAW:
10667             if (appData.debugMode)
10668               fprintf(debugFP, "Restoring %s for game %d\n",
10669                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10670
10671             thinkOutput[0] = NULLCHAR;
10672             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10673             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10674             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10675             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10676             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10677             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10678             MakeMove(fromX, fromY, toX, toY, promoChar);
10679             ShowMove(fromX, fromY, toX, toY);
10680
10681             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10682               case MT_NONE:
10683               case MT_CHECK:
10684                 break;
10685
10686               case MT_CHECKMATE:
10687               case MT_STAINMATE:
10688                 if (WhiteOnMove(currentMove)) {
10689                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10690                 } else {
10691                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10692                 }
10693                 break;
10694
10695               case MT_STALEMATE:
10696                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10697                 break;
10698             }
10699
10700             break;
10701
10702           case CMAIL_RESIGN:
10703             if (WhiteOnMove(currentMove)) {
10704                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10705             } else {
10706                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10707             }
10708             break;
10709
10710           case CMAIL_ACCEPT:
10711             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10712             break;
10713
10714           default:
10715             break;
10716         }
10717     }
10718
10719     return;
10720 }
10721
10722 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10723 int
10724 CmailLoadGame(f, gameNumber, title, useList)
10725      FILE *f;
10726      int gameNumber;
10727      char *title;
10728      int useList;
10729 {
10730     int retVal;
10731
10732     if (gameNumber > nCmailGames) {
10733         DisplayError(_("No more games in this message"), 0);
10734         return FALSE;
10735     }
10736     if (f == lastLoadGameFP) {
10737         int offset = gameNumber - lastLoadGameNumber;
10738         if (offset == 0) {
10739             cmailMsg[0] = NULLCHAR;
10740             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10741                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10742                 nCmailMovesRegistered--;
10743             }
10744             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10745             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10746                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10747             }
10748         } else {
10749             if (! RegisterMove()) return FALSE;
10750         }
10751     }
10752
10753     retVal = LoadGame(f, gameNumber, title, useList);
10754
10755     /* Make move registered during previous look at this game, if any */
10756     MakeRegisteredMove();
10757
10758     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10759         commentList[currentMove]
10760           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10761         DisplayComment(currentMove - 1, commentList[currentMove]);
10762     }
10763
10764     return retVal;
10765 }
10766
10767 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10768 int
10769 ReloadGame(offset)
10770      int offset;
10771 {
10772     int gameNumber = lastLoadGameNumber + offset;
10773     if (lastLoadGameFP == NULL) {
10774         DisplayError(_("No game has been loaded yet"), 0);
10775         return FALSE;
10776     }
10777     if (gameNumber <= 0) {
10778         DisplayError(_("Can't back up any further"), 0);
10779         return FALSE;
10780     }
10781     if (cmailMsgLoaded) {
10782         return CmailLoadGame(lastLoadGameFP, gameNumber,
10783                              lastLoadGameTitle, lastLoadGameUseList);
10784     } else {
10785         return LoadGame(lastLoadGameFP, gameNumber,
10786                         lastLoadGameTitle, lastLoadGameUseList);
10787     }
10788 }
10789
10790
10791
10792 /* Load the nth game from open file f */
10793 int
10794 LoadGame(f, gameNumber, title, useList)
10795      FILE *f;
10796      int gameNumber;
10797      char *title;
10798      int useList;
10799 {
10800     ChessMove cm;
10801     char buf[MSG_SIZ];
10802     int gn = gameNumber;
10803     ListGame *lg = NULL;
10804     int numPGNTags = 0;
10805     int err;
10806     GameMode oldGameMode;
10807     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10808
10809     if (appData.debugMode)
10810         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10811
10812     if (gameMode == Training )
10813         SetTrainingModeOff();
10814
10815     oldGameMode = gameMode;
10816     if (gameMode != BeginningOfGame) {
10817       Reset(FALSE, TRUE);
10818     }
10819
10820     gameFileFP = f;
10821     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10822         fclose(lastLoadGameFP);
10823     }
10824
10825     if (useList) {
10826         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10827
10828         if (lg) {
10829             fseek(f, lg->offset, 0);
10830             GameListHighlight(gameNumber);
10831             gn = 1;
10832         }
10833         else {
10834             DisplayError(_("Game number out of range"), 0);
10835             return FALSE;
10836         }
10837     } else {
10838         GameListDestroy();
10839         if (fseek(f, 0, 0) == -1) {
10840             if (f == lastLoadGameFP ?
10841                 gameNumber == lastLoadGameNumber + 1 :
10842                 gameNumber == 1) {
10843                 gn = 1;
10844             } else {
10845                 DisplayError(_("Can't seek on game file"), 0);
10846                 return FALSE;
10847             }
10848         }
10849     }
10850     lastLoadGameFP = f;
10851     lastLoadGameNumber = gameNumber;
10852     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10853     lastLoadGameUseList = useList;
10854
10855     yynewfile(f);
10856
10857     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10858       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10859                 lg->gameInfo.black);
10860             DisplayTitle(buf);
10861     } else if (*title != NULLCHAR) {
10862         if (gameNumber > 1) {
10863           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10864             DisplayTitle(buf);
10865         } else {
10866             DisplayTitle(title);
10867         }
10868     }
10869
10870     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10871         gameMode = PlayFromGameFile;
10872         ModeHighlight();
10873     }
10874
10875     currentMove = forwardMostMove = backwardMostMove = 0;
10876     CopyBoard(boards[0], initialPosition);
10877     StopClocks();
10878
10879     /*
10880      * Skip the first gn-1 games in the file.
10881      * Also skip over anything that precedes an identifiable
10882      * start of game marker, to avoid being confused by
10883      * garbage at the start of the file.  Currently
10884      * recognized start of game markers are the move number "1",
10885      * the pattern "gnuchess .* game", the pattern
10886      * "^[#;%] [^ ]* game file", and a PGN tag block.
10887      * A game that starts with one of the latter two patterns
10888      * will also have a move number 1, possibly
10889      * following a position diagram.
10890      * 5-4-02: Let's try being more lenient and allowing a game to
10891      * start with an unnumbered move.  Does that break anything?
10892      */
10893     cm = lastLoadGameStart = EndOfFile;
10894     while (gn > 0) {
10895         yyboardindex = forwardMostMove;
10896         cm = (ChessMove) Myylex();
10897         switch (cm) {
10898           case EndOfFile:
10899             if (cmailMsgLoaded) {
10900                 nCmailGames = CMAIL_MAX_GAMES - gn;
10901             } else {
10902                 Reset(TRUE, TRUE);
10903                 DisplayError(_("Game not found in file"), 0);
10904             }
10905             return FALSE;
10906
10907           case GNUChessGame:
10908           case XBoardGame:
10909             gn--;
10910             lastLoadGameStart = cm;
10911             break;
10912
10913           case MoveNumberOne:
10914             switch (lastLoadGameStart) {
10915               case GNUChessGame:
10916               case XBoardGame:
10917               case PGNTag:
10918                 break;
10919               case MoveNumberOne:
10920               case EndOfFile:
10921                 gn--;           /* count this game */
10922                 lastLoadGameStart = cm;
10923                 break;
10924               default:
10925                 /* impossible */
10926                 break;
10927             }
10928             break;
10929
10930           case PGNTag:
10931             switch (lastLoadGameStart) {
10932               case GNUChessGame:
10933               case PGNTag:
10934               case MoveNumberOne:
10935               case EndOfFile:
10936                 gn--;           /* count this game */
10937                 lastLoadGameStart = cm;
10938                 break;
10939               case XBoardGame:
10940                 lastLoadGameStart = cm; /* game counted already */
10941                 break;
10942               default:
10943                 /* impossible */
10944                 break;
10945             }
10946             if (gn > 0) {
10947                 do {
10948                     yyboardindex = forwardMostMove;
10949                     cm = (ChessMove) Myylex();
10950                 } while (cm == PGNTag || cm == Comment);
10951             }
10952             break;
10953
10954           case WhiteWins:
10955           case BlackWins:
10956           case GameIsDrawn:
10957             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10958                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10959                     != CMAIL_OLD_RESULT) {
10960                     nCmailResults ++ ;
10961                     cmailResult[  CMAIL_MAX_GAMES
10962                                 - gn - 1] = CMAIL_OLD_RESULT;
10963                 }
10964             }
10965             break;
10966
10967           case NormalMove:
10968             /* Only a NormalMove can be at the start of a game
10969              * without a position diagram. */
10970             if (lastLoadGameStart == EndOfFile ) {
10971               gn--;
10972               lastLoadGameStart = MoveNumberOne;
10973             }
10974             break;
10975
10976           default:
10977             break;
10978         }
10979     }
10980
10981     if (appData.debugMode)
10982       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10983
10984     if (cm == XBoardGame) {
10985         /* Skip any header junk before position diagram and/or move 1 */
10986         for (;;) {
10987             yyboardindex = forwardMostMove;
10988             cm = (ChessMove) Myylex();
10989
10990             if (cm == EndOfFile ||
10991                 cm == GNUChessGame || cm == XBoardGame) {
10992                 /* Empty game; pretend end-of-file and handle later */
10993                 cm = EndOfFile;
10994                 break;
10995             }
10996
10997             if (cm == MoveNumberOne || cm == PositionDiagram ||
10998                 cm == PGNTag || cm == Comment)
10999               break;
11000         }
11001     } else if (cm == GNUChessGame) {
11002         if (gameInfo.event != NULL) {
11003             free(gameInfo.event);
11004         }
11005         gameInfo.event = StrSave(yy_text);
11006     }
11007
11008     startedFromSetupPosition = FALSE;
11009     while (cm == PGNTag) {
11010         if (appData.debugMode)
11011           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11012         err = ParsePGNTag(yy_text, &gameInfo);
11013         if (!err) numPGNTags++;
11014
11015         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11016         if(gameInfo.variant != oldVariant) {
11017             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11018             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11019             InitPosition(TRUE);
11020             oldVariant = gameInfo.variant;
11021             if (appData.debugMode)
11022               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11023         }
11024
11025
11026         if (gameInfo.fen != NULL) {
11027           Board initial_position;
11028           startedFromSetupPosition = TRUE;
11029           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11030             Reset(TRUE, TRUE);
11031             DisplayError(_("Bad FEN position in file"), 0);
11032             return FALSE;
11033           }
11034           CopyBoard(boards[0], initial_position);
11035           if (blackPlaysFirst) {
11036             currentMove = forwardMostMove = backwardMostMove = 1;
11037             CopyBoard(boards[1], initial_position);
11038             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11039             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11040             timeRemaining[0][1] = whiteTimeRemaining;
11041             timeRemaining[1][1] = blackTimeRemaining;
11042             if (commentList[0] != NULL) {
11043               commentList[1] = commentList[0];
11044               commentList[0] = NULL;
11045             }
11046           } else {
11047             currentMove = forwardMostMove = backwardMostMove = 0;
11048           }
11049           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11050           {   int i;
11051               initialRulePlies = FENrulePlies;
11052               for( i=0; i< nrCastlingRights; i++ )
11053                   initialRights[i] = initial_position[CASTLING][i];
11054           }
11055           yyboardindex = forwardMostMove;
11056           free(gameInfo.fen);
11057           gameInfo.fen = NULL;
11058         }
11059
11060         yyboardindex = forwardMostMove;
11061         cm = (ChessMove) Myylex();
11062
11063         /* Handle comments interspersed among the tags */
11064         while (cm == Comment) {
11065             char *p;
11066             if (appData.debugMode)
11067               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11068             p = yy_text;
11069             AppendComment(currentMove, p, FALSE);
11070             yyboardindex = forwardMostMove;
11071             cm = (ChessMove) Myylex();
11072         }
11073     }
11074
11075     /* don't rely on existence of Event tag since if game was
11076      * pasted from clipboard the Event tag may not exist
11077      */
11078     if (numPGNTags > 0){
11079         char *tags;
11080         if (gameInfo.variant == VariantNormal) {
11081           VariantClass v = StringToVariant(gameInfo.event);
11082           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11083           if(v < VariantShogi) gameInfo.variant = v;
11084         }
11085         if (!matchMode) {
11086           if( appData.autoDisplayTags ) {
11087             tags = PGNTags(&gameInfo);
11088             TagsPopUp(tags, CmailMsg());
11089             free(tags);
11090           }
11091         }
11092     } else {
11093         /* Make something up, but don't display it now */
11094         SetGameInfo();
11095         TagsPopDown();
11096     }
11097
11098     if (cm == PositionDiagram) {
11099         int i, j;
11100         char *p;
11101         Board initial_position;
11102
11103         if (appData.debugMode)
11104           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11105
11106         if (!startedFromSetupPosition) {
11107             p = yy_text;
11108             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11109               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11110                 switch (*p) {
11111                   case '{':
11112                   case '[':
11113                   case '-':
11114                   case ' ':
11115                   case '\t':
11116                   case '\n':
11117                   case '\r':
11118                     break;
11119                   default:
11120                     initial_position[i][j++] = CharToPiece(*p);
11121                     break;
11122                 }
11123             while (*p == ' ' || *p == '\t' ||
11124                    *p == '\n' || *p == '\r') p++;
11125
11126             if (strncmp(p, "black", strlen("black"))==0)
11127               blackPlaysFirst = TRUE;
11128             else
11129               blackPlaysFirst = FALSE;
11130             startedFromSetupPosition = TRUE;
11131
11132             CopyBoard(boards[0], initial_position);
11133             if (blackPlaysFirst) {
11134                 currentMove = forwardMostMove = backwardMostMove = 1;
11135                 CopyBoard(boards[1], initial_position);
11136                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11137                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11138                 timeRemaining[0][1] = whiteTimeRemaining;
11139                 timeRemaining[1][1] = blackTimeRemaining;
11140                 if (commentList[0] != NULL) {
11141                     commentList[1] = commentList[0];
11142                     commentList[0] = NULL;
11143                 }
11144             } else {
11145                 currentMove = forwardMostMove = backwardMostMove = 0;
11146             }
11147         }
11148         yyboardindex = forwardMostMove;
11149         cm = (ChessMove) Myylex();
11150     }
11151
11152     if (first.pr == NoProc) {
11153         StartChessProgram(&first);
11154     }
11155     InitChessProgram(&first, FALSE);
11156     SendToProgram("force\n", &first);
11157     if (startedFromSetupPosition) {
11158         SendBoard(&first, forwardMostMove);
11159     if (appData.debugMode) {
11160         fprintf(debugFP, "Load Game\n");
11161     }
11162         DisplayBothClocks();
11163     }
11164
11165     /* [HGM] server: flag to write setup moves in broadcast file as one */
11166     loadFlag = appData.suppressLoadMoves;
11167
11168     while (cm == Comment) {
11169         char *p;
11170         if (appData.debugMode)
11171           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11172         p = yy_text;
11173         AppendComment(currentMove, p, FALSE);
11174         yyboardindex = forwardMostMove;
11175         cm = (ChessMove) Myylex();
11176     }
11177
11178     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11179         cm == WhiteWins || cm == BlackWins ||
11180         cm == GameIsDrawn || cm == GameUnfinished) {
11181         DisplayMessage("", _("No moves in game"));
11182         if (cmailMsgLoaded) {
11183             if (appData.debugMode)
11184               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11185             ClearHighlights();
11186             flipView = FALSE;
11187         }
11188         DrawPosition(FALSE, boards[currentMove]);
11189         DisplayBothClocks();
11190         gameMode = EditGame;
11191         ModeHighlight();
11192         gameFileFP = NULL;
11193         cmailOldMove = 0;
11194         return TRUE;
11195     }
11196
11197     // [HGM] PV info: routine tests if comment empty
11198     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11199         DisplayComment(currentMove - 1, commentList[currentMove]);
11200     }
11201     if (!matchMode && appData.timeDelay != 0)
11202       DrawPosition(FALSE, boards[currentMove]);
11203
11204     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11205       programStats.ok_to_send = 1;
11206     }
11207
11208     /* if the first token after the PGN tags is a move
11209      * and not move number 1, retrieve it from the parser
11210      */
11211     if (cm != MoveNumberOne)
11212         LoadGameOneMove(cm);
11213
11214     /* load the remaining moves from the file */
11215     while (LoadGameOneMove(EndOfFile)) {
11216       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11217       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11218     }
11219
11220     /* rewind to the start of the game */
11221     currentMove = backwardMostMove;
11222
11223     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11224
11225     if (oldGameMode == AnalyzeFile ||
11226         oldGameMode == AnalyzeMode) {
11227       AnalyzeFileEvent();
11228     }
11229
11230     if (matchMode || appData.timeDelay == 0) {
11231       ToEndEvent();
11232       gameMode = EditGame;
11233       ModeHighlight();
11234     } else if (appData.timeDelay > 0) {
11235       AutoPlayGameLoop();
11236     }
11237
11238     if (appData.debugMode)
11239         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11240
11241     loadFlag = 0; /* [HGM] true game starts */
11242     return TRUE;
11243 }
11244
11245 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11246 int
11247 ReloadPosition(offset)
11248      int offset;
11249 {
11250     int positionNumber = lastLoadPositionNumber + offset;
11251     if (lastLoadPositionFP == NULL) {
11252         DisplayError(_("No position has been loaded yet"), 0);
11253         return FALSE;
11254     }
11255     if (positionNumber <= 0) {
11256         DisplayError(_("Can't back up any further"), 0);
11257         return FALSE;
11258     }
11259     return LoadPosition(lastLoadPositionFP, positionNumber,
11260                         lastLoadPositionTitle);
11261 }
11262
11263 /* Load the nth position from the given file */
11264 int
11265 LoadPositionFromFile(filename, n, title)
11266      char *filename;
11267      int n;
11268      char *title;
11269 {
11270     FILE *f;
11271     char buf[MSG_SIZ];
11272
11273     if (strcmp(filename, "-") == 0) {
11274         return LoadPosition(stdin, n, "stdin");
11275     } else {
11276         f = fopen(filename, "rb");
11277         if (f == NULL) {
11278             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11279             DisplayError(buf, errno);
11280             return FALSE;
11281         } else {
11282             return LoadPosition(f, n, title);
11283         }
11284     }
11285 }
11286
11287 /* Load the nth position from the given open file, and close it */
11288 int
11289 LoadPosition(f, positionNumber, title)
11290      FILE *f;
11291      int positionNumber;
11292      char *title;
11293 {
11294     char *p, line[MSG_SIZ];
11295     Board initial_position;
11296     int i, j, fenMode, pn;
11297
11298     if (gameMode == Training )
11299         SetTrainingModeOff();
11300
11301     if (gameMode != BeginningOfGame) {
11302         Reset(FALSE, TRUE);
11303     }
11304     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11305         fclose(lastLoadPositionFP);
11306     }
11307     if (positionNumber == 0) positionNumber = 1;
11308     lastLoadPositionFP = f;
11309     lastLoadPositionNumber = positionNumber;
11310     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11311     if (first.pr == NoProc) {
11312       StartChessProgram(&first);
11313       InitChessProgram(&first, FALSE);
11314     }
11315     pn = positionNumber;
11316     if (positionNumber < 0) {
11317         /* Negative position number means to seek to that byte offset */
11318         if (fseek(f, -positionNumber, 0) == -1) {
11319             DisplayError(_("Can't seek on position file"), 0);
11320             return FALSE;
11321         };
11322         pn = 1;
11323     } else {
11324         if (fseek(f, 0, 0) == -1) {
11325             if (f == lastLoadPositionFP ?
11326                 positionNumber == lastLoadPositionNumber + 1 :
11327                 positionNumber == 1) {
11328                 pn = 1;
11329             } else {
11330                 DisplayError(_("Can't seek on position file"), 0);
11331                 return FALSE;
11332             }
11333         }
11334     }
11335     /* See if this file is FEN or old-style xboard */
11336     if (fgets(line, MSG_SIZ, f) == NULL) {
11337         DisplayError(_("Position not found in file"), 0);
11338         return FALSE;
11339     }
11340     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11341     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11342
11343     if (pn >= 2) {
11344         if (fenMode || line[0] == '#') pn--;
11345         while (pn > 0) {
11346             /* skip positions before number pn */
11347             if (fgets(line, MSG_SIZ, f) == NULL) {
11348                 Reset(TRUE, TRUE);
11349                 DisplayError(_("Position not found in file"), 0);
11350                 return FALSE;
11351             }
11352             if (fenMode || line[0] == '#') pn--;
11353         }
11354     }
11355
11356     if (fenMode) {
11357         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11358             DisplayError(_("Bad FEN position in file"), 0);
11359             return FALSE;
11360         }
11361     } else {
11362         (void) fgets(line, MSG_SIZ, f);
11363         (void) fgets(line, MSG_SIZ, f);
11364
11365         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11366             (void) fgets(line, MSG_SIZ, f);
11367             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11368                 if (*p == ' ')
11369                   continue;
11370                 initial_position[i][j++] = CharToPiece(*p);
11371             }
11372         }
11373
11374         blackPlaysFirst = FALSE;
11375         if (!feof(f)) {
11376             (void) fgets(line, MSG_SIZ, f);
11377             if (strncmp(line, "black", strlen("black"))==0)
11378               blackPlaysFirst = TRUE;
11379         }
11380     }
11381     startedFromSetupPosition = TRUE;
11382
11383     SendToProgram("force\n", &first);
11384     CopyBoard(boards[0], initial_position);
11385     if (blackPlaysFirst) {
11386         currentMove = forwardMostMove = backwardMostMove = 1;
11387         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11388         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11389         CopyBoard(boards[1], initial_position);
11390         DisplayMessage("", _("Black to play"));
11391     } else {
11392         currentMove = forwardMostMove = backwardMostMove = 0;
11393         DisplayMessage("", _("White to play"));
11394     }
11395     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11396     SendBoard(&first, forwardMostMove);
11397     if (appData.debugMode) {
11398 int i, j;
11399   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11400   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11401         fprintf(debugFP, "Load Position\n");
11402     }
11403
11404     if (positionNumber > 1) {
11405       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11406         DisplayTitle(line);
11407     } else {
11408         DisplayTitle(title);
11409     }
11410     gameMode = EditGame;
11411     ModeHighlight();
11412     ResetClocks();
11413     timeRemaining[0][1] = whiteTimeRemaining;
11414     timeRemaining[1][1] = blackTimeRemaining;
11415     DrawPosition(FALSE, boards[currentMove]);
11416
11417     return TRUE;
11418 }
11419
11420
11421 void
11422 CopyPlayerNameIntoFileName(dest, src)
11423      char **dest, *src;
11424 {
11425     while (*src != NULLCHAR && *src != ',') {
11426         if (*src == ' ') {
11427             *(*dest)++ = '_';
11428             src++;
11429         } else {
11430             *(*dest)++ = *src++;
11431         }
11432     }
11433 }
11434
11435 char *DefaultFileName(ext)
11436      char *ext;
11437 {
11438     static char def[MSG_SIZ];
11439     char *p;
11440
11441     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11442         p = def;
11443         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11444         *p++ = '-';
11445         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11446         *p++ = '.';
11447         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11448     } else {
11449         def[0] = NULLCHAR;
11450     }
11451     return def;
11452 }
11453
11454 /* Save the current game to the given file */
11455 int
11456 SaveGameToFile(filename, append)
11457      char *filename;
11458      int append;
11459 {
11460     FILE *f;
11461     char buf[MSG_SIZ];
11462     int result;
11463
11464     if (strcmp(filename, "-") == 0) {
11465         return SaveGame(stdout, 0, NULL);
11466     } else {
11467         f = fopen(filename, append ? "a" : "w");
11468         if (f == NULL) {
11469             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11470             DisplayError(buf, errno);
11471             return FALSE;
11472         } else {
11473             safeStrCpy(buf, lastMsg, MSG_SIZ);
11474             DisplayMessage(_("Waiting for access to save file"), "");
11475             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11476             DisplayMessage(_("Saving game"), "");
11477             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11478             result = SaveGame(f, 0, NULL);
11479             DisplayMessage(buf, "");
11480             return result;
11481         }
11482     }
11483 }
11484
11485 char *
11486 SavePart(str)
11487      char *str;
11488 {
11489     static char buf[MSG_SIZ];
11490     char *p;
11491
11492     p = strchr(str, ' ');
11493     if (p == NULL) return str;
11494     strncpy(buf, str, p - str);
11495     buf[p - str] = NULLCHAR;
11496     return buf;
11497 }
11498
11499 #define PGN_MAX_LINE 75
11500
11501 #define PGN_SIDE_WHITE  0
11502 #define PGN_SIDE_BLACK  1
11503
11504 /* [AS] */
11505 static int FindFirstMoveOutOfBook( int side )
11506 {
11507     int result = -1;
11508
11509     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11510         int index = backwardMostMove;
11511         int has_book_hit = 0;
11512
11513         if( (index % 2) != side ) {
11514             index++;
11515         }
11516
11517         while( index < forwardMostMove ) {
11518             /* Check to see if engine is in book */
11519             int depth = pvInfoList[index].depth;
11520             int score = pvInfoList[index].score;
11521             int in_book = 0;
11522
11523             if( depth <= 2 ) {
11524                 in_book = 1;
11525             }
11526             else if( score == 0 && depth == 63 ) {
11527                 in_book = 1; /* Zappa */
11528             }
11529             else if( score == 2 && depth == 99 ) {
11530                 in_book = 1; /* Abrok */
11531             }
11532
11533             has_book_hit += in_book;
11534
11535             if( ! in_book ) {
11536                 result = index;
11537
11538                 break;
11539             }
11540
11541             index += 2;
11542         }
11543     }
11544
11545     return result;
11546 }
11547
11548 /* [AS] */
11549 void GetOutOfBookInfo( char * buf )
11550 {
11551     int oob[2];
11552     int i;
11553     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11554
11555     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11556     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11557
11558     *buf = '\0';
11559
11560     if( oob[0] >= 0 || oob[1] >= 0 ) {
11561         for( i=0; i<2; i++ ) {
11562             int idx = oob[i];
11563
11564             if( idx >= 0 ) {
11565                 if( i > 0 && oob[0] >= 0 ) {
11566                     strcat( buf, "   " );
11567                 }
11568
11569                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11570                 sprintf( buf+strlen(buf), "%s%.2f",
11571                     pvInfoList[idx].score >= 0 ? "+" : "",
11572                     pvInfoList[idx].score / 100.0 );
11573             }
11574         }
11575     }
11576 }
11577
11578 /* Save game in PGN style and close the file */
11579 int
11580 SaveGamePGN(f)
11581      FILE *f;
11582 {
11583     int i, offset, linelen, newblock;
11584     time_t tm;
11585 //    char *movetext;
11586     char numtext[32];
11587     int movelen, numlen, blank;
11588     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11589
11590     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11591
11592     tm = time((time_t *) NULL);
11593
11594     PrintPGNTags(f, &gameInfo);
11595
11596     if (backwardMostMove > 0 || startedFromSetupPosition) {
11597         char *fen = PositionToFEN(backwardMostMove, NULL);
11598         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11599         fprintf(f, "\n{--------------\n");
11600         PrintPosition(f, backwardMostMove);
11601         fprintf(f, "--------------}\n");
11602         free(fen);
11603     }
11604     else {
11605         /* [AS] Out of book annotation */
11606         if( appData.saveOutOfBookInfo ) {
11607             char buf[64];
11608
11609             GetOutOfBookInfo( buf );
11610
11611             if( buf[0] != '\0' ) {
11612                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11613             }
11614         }
11615
11616         fprintf(f, "\n");
11617     }
11618
11619     i = backwardMostMove;
11620     linelen = 0;
11621     newblock = TRUE;
11622
11623     while (i < forwardMostMove) {
11624         /* Print comments preceding this move */
11625         if (commentList[i] != NULL) {
11626             if (linelen > 0) fprintf(f, "\n");
11627             fprintf(f, "%s", commentList[i]);
11628             linelen = 0;
11629             newblock = TRUE;
11630         }
11631
11632         /* Format move number */
11633         if ((i % 2) == 0)
11634           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11635         else
11636           if (newblock)
11637             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11638           else
11639             numtext[0] = NULLCHAR;
11640
11641         numlen = strlen(numtext);
11642         newblock = FALSE;
11643
11644         /* Print move number */
11645         blank = linelen > 0 && numlen > 0;
11646         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11647             fprintf(f, "\n");
11648             linelen = 0;
11649             blank = 0;
11650         }
11651         if (blank) {
11652             fprintf(f, " ");
11653             linelen++;
11654         }
11655         fprintf(f, "%s", numtext);
11656         linelen += numlen;
11657
11658         /* Get move */
11659         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11660         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11661
11662         /* Print move */
11663         blank = linelen > 0 && movelen > 0;
11664         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11665             fprintf(f, "\n");
11666             linelen = 0;
11667             blank = 0;
11668         }
11669         if (blank) {
11670             fprintf(f, " ");
11671             linelen++;
11672         }
11673         fprintf(f, "%s", move_buffer);
11674         linelen += movelen;
11675
11676         /* [AS] Add PV info if present */
11677         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11678             /* [HGM] add time */
11679             char buf[MSG_SIZ]; int seconds;
11680
11681             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11682
11683             if( seconds <= 0)
11684               buf[0] = 0;
11685             else
11686               if( seconds < 30 )
11687                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11688               else
11689                 {
11690                   seconds = (seconds + 4)/10; // round to full seconds
11691                   if( seconds < 60 )
11692                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11693                   else
11694                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11695                 }
11696
11697             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11698                       pvInfoList[i].score >= 0 ? "+" : "",
11699                       pvInfoList[i].score / 100.0,
11700                       pvInfoList[i].depth,
11701                       buf );
11702
11703             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11704
11705             /* Print score/depth */
11706             blank = linelen > 0 && movelen > 0;
11707             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11708                 fprintf(f, "\n");
11709                 linelen = 0;
11710                 blank = 0;
11711             }
11712             if (blank) {
11713                 fprintf(f, " ");
11714                 linelen++;
11715             }
11716             fprintf(f, "%s", move_buffer);
11717             linelen += movelen;
11718         }
11719
11720         i++;
11721     }
11722
11723     /* Start a new line */
11724     if (linelen > 0) fprintf(f, "\n");
11725
11726     /* Print comments after last move */
11727     if (commentList[i] != NULL) {
11728         fprintf(f, "%s\n", commentList[i]);
11729     }
11730
11731     /* Print result */
11732     if (gameInfo.resultDetails != NULL &&
11733         gameInfo.resultDetails[0] != NULLCHAR) {
11734         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11735                 PGNResult(gameInfo.result));
11736     } else {
11737         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11738     }
11739
11740     fclose(f);
11741     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11742     return TRUE;
11743 }
11744
11745 /* Save game in old style and close the file */
11746 int
11747 SaveGameOldStyle(f)
11748      FILE *f;
11749 {
11750     int i, offset;
11751     time_t tm;
11752
11753     tm = time((time_t *) NULL);
11754
11755     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11756     PrintOpponents(f);
11757
11758     if (backwardMostMove > 0 || startedFromSetupPosition) {
11759         fprintf(f, "\n[--------------\n");
11760         PrintPosition(f, backwardMostMove);
11761         fprintf(f, "--------------]\n");
11762     } else {
11763         fprintf(f, "\n");
11764     }
11765
11766     i = backwardMostMove;
11767     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11768
11769     while (i < forwardMostMove) {
11770         if (commentList[i] != NULL) {
11771             fprintf(f, "[%s]\n", commentList[i]);
11772         }
11773
11774         if ((i % 2) == 1) {
11775             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11776             i++;
11777         } else {
11778             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11779             i++;
11780             if (commentList[i] != NULL) {
11781                 fprintf(f, "\n");
11782                 continue;
11783             }
11784             if (i >= forwardMostMove) {
11785                 fprintf(f, "\n");
11786                 break;
11787             }
11788             fprintf(f, "%s\n", parseList[i]);
11789             i++;
11790         }
11791     }
11792
11793     if (commentList[i] != NULL) {
11794         fprintf(f, "[%s]\n", commentList[i]);
11795     }
11796
11797     /* This isn't really the old style, but it's close enough */
11798     if (gameInfo.resultDetails != NULL &&
11799         gameInfo.resultDetails[0] != NULLCHAR) {
11800         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11801                 gameInfo.resultDetails);
11802     } else {
11803         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11804     }
11805
11806     fclose(f);
11807     return TRUE;
11808 }
11809
11810 /* Save the current game to open file f and close the file */
11811 int
11812 SaveGame(f, dummy, dummy2)
11813      FILE *f;
11814      int dummy;
11815      char *dummy2;
11816 {
11817     if (gameMode == EditPosition) EditPositionDone(TRUE);
11818     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11819     if (appData.oldSaveStyle)
11820       return SaveGameOldStyle(f);
11821     else
11822       return SaveGamePGN(f);
11823 }
11824
11825 /* Save the current position to the given file */
11826 int
11827 SavePositionToFile(filename)
11828      char *filename;
11829 {
11830     FILE *f;
11831     char buf[MSG_SIZ];
11832
11833     if (strcmp(filename, "-") == 0) {
11834         return SavePosition(stdout, 0, NULL);
11835     } else {
11836         f = fopen(filename, "a");
11837         if (f == NULL) {
11838             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11839             DisplayError(buf, errno);
11840             return FALSE;
11841         } else {
11842             safeStrCpy(buf, lastMsg, MSG_SIZ);
11843             DisplayMessage(_("Waiting for access to save file"), "");
11844             flock(fileno(f), LOCK_EX); // [HGM] lock
11845             DisplayMessage(_("Saving position"), "");
11846             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11847             SavePosition(f, 0, NULL);
11848             DisplayMessage(buf, "");
11849             return TRUE;
11850         }
11851     }
11852 }
11853
11854 /* Save the current position to the given open file and close the file */
11855 int
11856 SavePosition(f, dummy, dummy2)
11857      FILE *f;
11858      int dummy;
11859      char *dummy2;
11860 {
11861     time_t tm;
11862     char *fen;
11863
11864     if (gameMode == EditPosition) EditPositionDone(TRUE);
11865     if (appData.oldSaveStyle) {
11866         tm = time((time_t *) NULL);
11867
11868         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11869         PrintOpponents(f);
11870         fprintf(f, "[--------------\n");
11871         PrintPosition(f, currentMove);
11872         fprintf(f, "--------------]\n");
11873     } else {
11874         fen = PositionToFEN(currentMove, NULL);
11875         fprintf(f, "%s\n", fen);
11876         free(fen);
11877     }
11878     fclose(f);
11879     return TRUE;
11880 }
11881
11882 void
11883 ReloadCmailMsgEvent(unregister)
11884      int unregister;
11885 {
11886 #if !WIN32
11887     static char *inFilename = NULL;
11888     static char *outFilename;
11889     int i;
11890     struct stat inbuf, outbuf;
11891     int status;
11892
11893     /* Any registered moves are unregistered if unregister is set, */
11894     /* i.e. invoked by the signal handler */
11895     if (unregister) {
11896         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11897             cmailMoveRegistered[i] = FALSE;
11898             if (cmailCommentList[i] != NULL) {
11899                 free(cmailCommentList[i]);
11900                 cmailCommentList[i] = NULL;
11901             }
11902         }
11903         nCmailMovesRegistered = 0;
11904     }
11905
11906     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11907         cmailResult[i] = CMAIL_NOT_RESULT;
11908     }
11909     nCmailResults = 0;
11910
11911     if (inFilename == NULL) {
11912         /* Because the filenames are static they only get malloced once  */
11913         /* and they never get freed                                      */
11914         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11915         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11916
11917         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11918         sprintf(outFilename, "%s.out", appData.cmailGameName);
11919     }
11920
11921     status = stat(outFilename, &outbuf);
11922     if (status < 0) {
11923         cmailMailedMove = FALSE;
11924     } else {
11925         status = stat(inFilename, &inbuf);
11926         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11927     }
11928
11929     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11930        counts the games, notes how each one terminated, etc.
11931
11932        It would be nice to remove this kludge and instead gather all
11933        the information while building the game list.  (And to keep it
11934        in the game list nodes instead of having a bunch of fixed-size
11935        parallel arrays.)  Note this will require getting each game's
11936        termination from the PGN tags, as the game list builder does
11937        not process the game moves.  --mann
11938        */
11939     cmailMsgLoaded = TRUE;
11940     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11941
11942     /* Load first game in the file or popup game menu */
11943     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11944
11945 #endif /* !WIN32 */
11946     return;
11947 }
11948
11949 int
11950 RegisterMove()
11951 {
11952     FILE *f;
11953     char string[MSG_SIZ];
11954
11955     if (   cmailMailedMove
11956         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11957         return TRUE;            /* Allow free viewing  */
11958     }
11959
11960     /* Unregister move to ensure that we don't leave RegisterMove        */
11961     /* with the move registered when the conditions for registering no   */
11962     /* longer hold                                                       */
11963     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11964         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11965         nCmailMovesRegistered --;
11966
11967         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11968           {
11969               free(cmailCommentList[lastLoadGameNumber - 1]);
11970               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11971           }
11972     }
11973
11974     if (cmailOldMove == -1) {
11975         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11976         return FALSE;
11977     }
11978
11979     if (currentMove > cmailOldMove + 1) {
11980         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11981         return FALSE;
11982     }
11983
11984     if (currentMove < cmailOldMove) {
11985         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11986         return FALSE;
11987     }
11988
11989     if (forwardMostMove > currentMove) {
11990         /* Silently truncate extra moves */
11991         TruncateGame();
11992     }
11993
11994     if (   (currentMove == cmailOldMove + 1)
11995         || (   (currentMove == cmailOldMove)
11996             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11997                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11998         if (gameInfo.result != GameUnfinished) {
11999             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12000         }
12001
12002         if (commentList[currentMove] != NULL) {
12003             cmailCommentList[lastLoadGameNumber - 1]
12004               = StrSave(commentList[currentMove]);
12005         }
12006         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12007
12008         if (appData.debugMode)
12009           fprintf(debugFP, "Saving %s for game %d\n",
12010                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12011
12012         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12013
12014         f = fopen(string, "w");
12015         if (appData.oldSaveStyle) {
12016             SaveGameOldStyle(f); /* also closes the file */
12017
12018             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12019             f = fopen(string, "w");
12020             SavePosition(f, 0, NULL); /* also closes the file */
12021         } else {
12022             fprintf(f, "{--------------\n");
12023             PrintPosition(f, currentMove);
12024             fprintf(f, "--------------}\n\n");
12025
12026             SaveGame(f, 0, NULL); /* also closes the file*/
12027         }
12028
12029         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12030         nCmailMovesRegistered ++;
12031     } else if (nCmailGames == 1) {
12032         DisplayError(_("You have not made a move yet"), 0);
12033         return FALSE;
12034     }
12035
12036     return TRUE;
12037 }
12038
12039 void
12040 MailMoveEvent()
12041 {
12042 #if !WIN32
12043     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12044     FILE *commandOutput;
12045     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12046     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12047     int nBuffers;
12048     int i;
12049     int archived;
12050     char *arcDir;
12051
12052     if (! cmailMsgLoaded) {
12053         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12054         return;
12055     }
12056
12057     if (nCmailGames == nCmailResults) {
12058         DisplayError(_("No unfinished games"), 0);
12059         return;
12060     }
12061
12062 #if CMAIL_PROHIBIT_REMAIL
12063     if (cmailMailedMove) {
12064       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);
12065         DisplayError(msg, 0);
12066         return;
12067     }
12068 #endif
12069
12070     if (! (cmailMailedMove || RegisterMove())) return;
12071
12072     if (   cmailMailedMove
12073         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12074       snprintf(string, MSG_SIZ, partCommandString,
12075                appData.debugMode ? " -v" : "", appData.cmailGameName);
12076         commandOutput = popen(string, "r");
12077
12078         if (commandOutput == NULL) {
12079             DisplayError(_("Failed to invoke cmail"), 0);
12080         } else {
12081             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12082                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12083             }
12084             if (nBuffers > 1) {
12085                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12086                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12087                 nBytes = MSG_SIZ - 1;
12088             } else {
12089                 (void) memcpy(msg, buffer, nBytes);
12090             }
12091             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12092
12093             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12094                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12095
12096                 archived = TRUE;
12097                 for (i = 0; i < nCmailGames; i ++) {
12098                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12099                         archived = FALSE;
12100                     }
12101                 }
12102                 if (   archived
12103                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12104                         != NULL)) {
12105                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12106                            arcDir,
12107                            appData.cmailGameName,
12108                            gameInfo.date);
12109                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12110                     cmailMsgLoaded = FALSE;
12111                 }
12112             }
12113
12114             DisplayInformation(msg);
12115             pclose(commandOutput);
12116         }
12117     } else {
12118         if ((*cmailMsg) != '\0') {
12119             DisplayInformation(cmailMsg);
12120         }
12121     }
12122
12123     return;
12124 #endif /* !WIN32 */
12125 }
12126
12127 char *
12128 CmailMsg()
12129 {
12130 #if WIN32
12131     return NULL;
12132 #else
12133     int  prependComma = 0;
12134     char number[5];
12135     char string[MSG_SIZ];       /* Space for game-list */
12136     int  i;
12137
12138     if (!cmailMsgLoaded) return "";
12139
12140     if (cmailMailedMove) {
12141       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12142     } else {
12143         /* Create a list of games left */
12144       snprintf(string, MSG_SIZ, "[");
12145         for (i = 0; i < nCmailGames; i ++) {
12146             if (! (   cmailMoveRegistered[i]
12147                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12148                 if (prependComma) {
12149                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12150                 } else {
12151                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12152                     prependComma = 1;
12153                 }
12154
12155                 strcat(string, number);
12156             }
12157         }
12158         strcat(string, "]");
12159
12160         if (nCmailMovesRegistered + nCmailResults == 0) {
12161             switch (nCmailGames) {
12162               case 1:
12163                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12164                 break;
12165
12166               case 2:
12167                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12168                 break;
12169
12170               default:
12171                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12172                          nCmailGames);
12173                 break;
12174             }
12175         } else {
12176             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12177               case 1:
12178                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12179                          string);
12180                 break;
12181
12182               case 0:
12183                 if (nCmailResults == nCmailGames) {
12184                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12185                 } else {
12186                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12187                 }
12188                 break;
12189
12190               default:
12191                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12192                          string);
12193             }
12194         }
12195     }
12196     return cmailMsg;
12197 #endif /* WIN32 */
12198 }
12199
12200 void
12201 ResetGameEvent()
12202 {
12203     if (gameMode == Training)
12204       SetTrainingModeOff();
12205
12206     Reset(TRUE, TRUE);
12207     cmailMsgLoaded = FALSE;
12208     if (appData.icsActive) {
12209       SendToICS(ics_prefix);
12210       SendToICS("refresh\n");
12211     }
12212 }
12213
12214 void
12215 ExitEvent(status)
12216      int status;
12217 {
12218     exiting++;
12219     if (exiting > 2) {
12220       /* Give up on clean exit */
12221       exit(status);
12222     }
12223     if (exiting > 1) {
12224       /* Keep trying for clean exit */
12225       return;
12226     }
12227
12228     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12229
12230     if (telnetISR != NULL) {
12231       RemoveInputSource(telnetISR);
12232     }
12233     if (icsPR != NoProc) {
12234       DestroyChildProcess(icsPR, TRUE);
12235     }
12236
12237     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12238     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12239
12240     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12241     /* make sure this other one finishes before killing it!                  */
12242     if(endingGame) { int count = 0;
12243         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12244         while(endingGame && count++ < 10) DoSleep(1);
12245         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12246     }
12247
12248     /* Kill off chess programs */
12249     if (first.pr != NoProc) {
12250         ExitAnalyzeMode();
12251
12252         DoSleep( appData.delayBeforeQuit );
12253         SendToProgram("quit\n", &first);
12254         DoSleep( appData.delayAfterQuit );
12255         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12256     }
12257     if (second.pr != NoProc) {
12258         DoSleep( appData.delayBeforeQuit );
12259         SendToProgram("quit\n", &second);
12260         DoSleep( appData.delayAfterQuit );
12261         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12262     }
12263     if (first.isr != NULL) {
12264         RemoveInputSource(first.isr);
12265     }
12266     if (second.isr != NULL) {
12267         RemoveInputSource(second.isr);
12268     }
12269
12270     ShutDownFrontEnd();
12271     exit(status);
12272 }
12273
12274 void
12275 PauseEvent()
12276 {
12277     if (appData.debugMode)
12278         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12279     if (pausing) {
12280         pausing = FALSE;
12281         ModeHighlight();
12282         if (gameMode == MachinePlaysWhite ||
12283             gameMode == MachinePlaysBlack) {
12284             StartClocks();
12285         } else {
12286             DisplayBothClocks();
12287         }
12288         if (gameMode == PlayFromGameFile) {
12289             if (appData.timeDelay >= 0)
12290                 AutoPlayGameLoop();
12291         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12292             Reset(FALSE, TRUE);
12293             SendToICS(ics_prefix);
12294             SendToICS("refresh\n");
12295         } else if (currentMove < forwardMostMove) {
12296             ForwardInner(forwardMostMove);
12297         }
12298         pauseExamInvalid = FALSE;
12299     } else {
12300         switch (gameMode) {
12301           default:
12302             return;
12303           case IcsExamining:
12304             pauseExamForwardMostMove = forwardMostMove;
12305             pauseExamInvalid = FALSE;
12306             /* fall through */
12307           case IcsObserving:
12308           case IcsPlayingWhite:
12309           case IcsPlayingBlack:
12310             pausing = TRUE;
12311             ModeHighlight();
12312             return;
12313           case PlayFromGameFile:
12314             (void) StopLoadGameTimer();
12315             pausing = TRUE;
12316             ModeHighlight();
12317             break;
12318           case BeginningOfGame:
12319             if (appData.icsActive) return;
12320             /* else fall through */
12321           case MachinePlaysWhite:
12322           case MachinePlaysBlack:
12323           case TwoMachinesPlay:
12324             if (forwardMostMove == 0)
12325               return;           /* don't pause if no one has moved */
12326             if ((gameMode == MachinePlaysWhite &&
12327                  !WhiteOnMove(forwardMostMove)) ||
12328                 (gameMode == MachinePlaysBlack &&
12329                  WhiteOnMove(forwardMostMove))) {
12330                 StopClocks();
12331             }
12332             pausing = TRUE;
12333             ModeHighlight();
12334             break;
12335         }
12336     }
12337 }
12338
12339 void
12340 EditCommentEvent()
12341 {
12342     char title[MSG_SIZ];
12343
12344     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12345       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12346     } else {
12347       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12348                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12349                parseList[currentMove - 1]);
12350     }
12351
12352     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12353 }
12354
12355
12356 void
12357 EditTagsEvent()
12358 {
12359     char *tags = PGNTags(&gameInfo);
12360     bookUp = FALSE;
12361     EditTagsPopUp(tags, NULL);
12362     free(tags);
12363 }
12364
12365 void
12366 AnalyzeModeEvent()
12367 {
12368     if (appData.noChessProgram || gameMode == AnalyzeMode)
12369       return;
12370
12371     if (gameMode != AnalyzeFile) {
12372         if (!appData.icsEngineAnalyze) {
12373                EditGameEvent();
12374                if (gameMode != EditGame) return;
12375         }
12376         ResurrectChessProgram();
12377         SendToProgram("analyze\n", &first);
12378         first.analyzing = TRUE;
12379         /*first.maybeThinking = TRUE;*/
12380         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12381         EngineOutputPopUp();
12382     }
12383     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12384     pausing = FALSE;
12385     ModeHighlight();
12386     SetGameInfo();
12387
12388     StartAnalysisClock();
12389     GetTimeMark(&lastNodeCountTime);
12390     lastNodeCount = 0;
12391 }
12392
12393 void
12394 AnalyzeFileEvent()
12395 {
12396     if (appData.noChessProgram || gameMode == AnalyzeFile)
12397       return;
12398
12399     if (gameMode != AnalyzeMode) {
12400         EditGameEvent();
12401         if (gameMode != EditGame) return;
12402         ResurrectChessProgram();
12403         SendToProgram("analyze\n", &first);
12404         first.analyzing = TRUE;
12405         /*first.maybeThinking = TRUE;*/
12406         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12407         EngineOutputPopUp();
12408     }
12409     gameMode = AnalyzeFile;
12410     pausing = FALSE;
12411     ModeHighlight();
12412     SetGameInfo();
12413
12414     StartAnalysisClock();
12415     GetTimeMark(&lastNodeCountTime);
12416     lastNodeCount = 0;
12417 }
12418
12419 void
12420 MachineWhiteEvent()
12421 {
12422     char buf[MSG_SIZ];
12423     char *bookHit = NULL;
12424
12425     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12426       return;
12427
12428
12429     if (gameMode == PlayFromGameFile ||
12430         gameMode == TwoMachinesPlay  ||
12431         gameMode == Training         ||
12432         gameMode == AnalyzeMode      ||
12433         gameMode == EndOfGame)
12434         EditGameEvent();
12435
12436     if (gameMode == EditPosition)
12437         EditPositionDone(TRUE);
12438
12439     if (!WhiteOnMove(currentMove)) {
12440         DisplayError(_("It is not White's turn"), 0);
12441         return;
12442     }
12443
12444     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12445       ExitAnalyzeMode();
12446
12447     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12448         gameMode == AnalyzeFile)
12449         TruncateGame();
12450
12451     ResurrectChessProgram();    /* in case it isn't running */
12452     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12453         gameMode = MachinePlaysWhite;
12454         ResetClocks();
12455     } else
12456     gameMode = MachinePlaysWhite;
12457     pausing = FALSE;
12458     ModeHighlight();
12459     SetGameInfo();
12460     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12461     DisplayTitle(buf);
12462     if (first.sendName) {
12463       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12464       SendToProgram(buf, &first);
12465     }
12466     if (first.sendTime) {
12467       if (first.useColors) {
12468         SendToProgram("black\n", &first); /*gnu kludge*/
12469       }
12470       SendTimeRemaining(&first, TRUE);
12471     }
12472     if (first.useColors) {
12473       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12474     }
12475     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12476     SetMachineThinkingEnables();
12477     first.maybeThinking = TRUE;
12478     StartClocks();
12479     firstMove = FALSE;
12480
12481     if (appData.autoFlipView && !flipView) {
12482       flipView = !flipView;
12483       DrawPosition(FALSE, NULL);
12484       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12485     }
12486
12487     if(bookHit) { // [HGM] book: simulate book reply
12488         static char bookMove[MSG_SIZ]; // a bit generous?
12489
12490         programStats.nodes = programStats.depth = programStats.time =
12491         programStats.score = programStats.got_only_move = 0;
12492         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12493
12494         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12495         strcat(bookMove, bookHit);
12496         HandleMachineMove(bookMove, &first);
12497     }
12498 }
12499
12500 void
12501 MachineBlackEvent()
12502 {
12503   char buf[MSG_SIZ];
12504   char *bookHit = NULL;
12505
12506     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12507         return;
12508
12509
12510     if (gameMode == PlayFromGameFile ||
12511         gameMode == TwoMachinesPlay  ||
12512         gameMode == Training         ||
12513         gameMode == AnalyzeMode      ||
12514         gameMode == EndOfGame)
12515         EditGameEvent();
12516
12517     if (gameMode == EditPosition)
12518         EditPositionDone(TRUE);
12519
12520     if (WhiteOnMove(currentMove)) {
12521         DisplayError(_("It is not Black's turn"), 0);
12522         return;
12523     }
12524
12525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12526       ExitAnalyzeMode();
12527
12528     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12529         gameMode == AnalyzeFile)
12530         TruncateGame();
12531
12532     ResurrectChessProgram();    /* in case it isn't running */
12533     gameMode = MachinePlaysBlack;
12534     pausing = FALSE;
12535     ModeHighlight();
12536     SetGameInfo();
12537     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12538     DisplayTitle(buf);
12539     if (first.sendName) {
12540       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12541       SendToProgram(buf, &first);
12542     }
12543     if (first.sendTime) {
12544       if (first.useColors) {
12545         SendToProgram("white\n", &first); /*gnu kludge*/
12546       }
12547       SendTimeRemaining(&first, FALSE);
12548     }
12549     if (first.useColors) {
12550       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12551     }
12552     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12553     SetMachineThinkingEnables();
12554     first.maybeThinking = TRUE;
12555     StartClocks();
12556
12557     if (appData.autoFlipView && flipView) {
12558       flipView = !flipView;
12559       DrawPosition(FALSE, NULL);
12560       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12561     }
12562     if(bookHit) { // [HGM] book: simulate book reply
12563         static char bookMove[MSG_SIZ]; // a bit generous?
12564
12565         programStats.nodes = programStats.depth = programStats.time =
12566         programStats.score = programStats.got_only_move = 0;
12567         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12568
12569         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12570         strcat(bookMove, bookHit);
12571         HandleMachineMove(bookMove, &first);
12572     }
12573 }
12574
12575
12576 void
12577 DisplayTwoMachinesTitle()
12578 {
12579     char buf[MSG_SIZ];
12580     if (appData.matchGames > 0) {
12581         if (first.twoMachinesColor[0] == 'w') {
12582           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12583                    gameInfo.white, gameInfo.black,
12584                    first.matchWins, second.matchWins,
12585                    matchGame - 1 - (first.matchWins + second.matchWins));
12586         } else {
12587           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12588                    gameInfo.white, gameInfo.black,
12589                    second.matchWins, first.matchWins,
12590                    matchGame - 1 - (first.matchWins + second.matchWins));
12591         }
12592     } else {
12593       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12594     }
12595     DisplayTitle(buf);
12596 }
12597
12598 void
12599 SettingsMenuIfReady()
12600 {
12601   if (second.lastPing != second.lastPong) {
12602     DisplayMessage("", _("Waiting for second chess program"));
12603     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12604     return;
12605   }
12606   ThawUI();
12607   DisplayMessage("", "");
12608   SettingsPopUp(&second);
12609 }
12610
12611 int
12612 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12613 {
12614     char buf[MSG_SIZ];
12615     if (cps->pr == NULL) {
12616         StartChessProgram(cps);
12617         if (cps->protocolVersion == 1) {
12618           retry();
12619         } else {
12620           /* kludge: allow timeout for initial "feature" command */
12621           FreezeUI();
12622           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12623           DisplayMessage("", buf);
12624           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12625         }
12626         return 1;
12627     }
12628     return 0;
12629 }
12630
12631 void
12632 TwoMachinesEvent P((void))
12633 {
12634     int i;
12635     char buf[MSG_SIZ];
12636     ChessProgramState *onmove;
12637     char *bookHit = NULL;
12638     static int stalling = 0;
12639     TimeMark now;
12640     long wait;
12641
12642     if (appData.noChessProgram) return;
12643
12644     switch (gameMode) {
12645       case TwoMachinesPlay:
12646         return;
12647       case MachinePlaysWhite:
12648       case MachinePlaysBlack:
12649         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12650             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12651             return;
12652         }
12653         /* fall through */
12654       case BeginningOfGame:
12655       case PlayFromGameFile:
12656       case EndOfGame:
12657         EditGameEvent();
12658         if (gameMode != EditGame) return;
12659         break;
12660       case EditPosition:
12661         EditPositionDone(TRUE);
12662         break;
12663       case AnalyzeMode:
12664       case AnalyzeFile:
12665         ExitAnalyzeMode();
12666         break;
12667       case EditGame:
12668       default:
12669         break;
12670     }
12671
12672 //    forwardMostMove = currentMove;
12673     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12674
12675     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12676
12677     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12678     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12679       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12680       return;
12681     }
12682     if(!stalling) {
12683       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12684       SendToProgram("force\n", &second);
12685       stalling = 1;
12686       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12687       return;
12688     }
12689     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12690     if(appData.matchPause>10000 || appData.matchPause<10)
12691                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12692     wait = SubtractTimeMarks(&now, &pauseStart);
12693     if(wait < appData.matchPause) {
12694         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12695         return;
12696     }
12697     stalling = 0;
12698     DisplayMessage("", "");
12699     if (startedFromSetupPosition) {
12700         SendBoard(&second, backwardMostMove);
12701     if (appData.debugMode) {
12702         fprintf(debugFP, "Two Machines\n");
12703     }
12704     }
12705     for (i = backwardMostMove; i < forwardMostMove; i++) {
12706         SendMoveToProgram(i, &second);
12707     }
12708
12709     gameMode = TwoMachinesPlay;
12710     pausing = FALSE;
12711     ModeHighlight();
12712     SetGameInfo();
12713     DisplayTwoMachinesTitle();
12714     firstMove = TRUE;
12715     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12716         onmove = &first;
12717     } else {
12718         onmove = &second;
12719     }
12720     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12721     SendToProgram(first.computerString, &first);
12722     if (first.sendName) {
12723       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12724       SendToProgram(buf, &first);
12725     }
12726     SendToProgram(second.computerString, &second);
12727     if (second.sendName) {
12728       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12729       SendToProgram(buf, &second);
12730     }
12731
12732     ResetClocks();
12733     if (!first.sendTime || !second.sendTime) {
12734         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12735         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12736     }
12737     if (onmove->sendTime) {
12738       if (onmove->useColors) {
12739         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12740       }
12741       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12742     }
12743     if (onmove->useColors) {
12744       SendToProgram(onmove->twoMachinesColor, onmove);
12745     }
12746     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12747 //    SendToProgram("go\n", onmove);
12748     onmove->maybeThinking = TRUE;
12749     SetMachineThinkingEnables();
12750
12751     StartClocks();
12752
12753     if(bookHit) { // [HGM] book: simulate book reply
12754         static char bookMove[MSG_SIZ]; // a bit generous?
12755
12756         programStats.nodes = programStats.depth = programStats.time =
12757         programStats.score = programStats.got_only_move = 0;
12758         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12759
12760         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12761         strcat(bookMove, bookHit);
12762         savedMessage = bookMove; // args for deferred call
12763         savedState = onmove;
12764         ScheduleDelayedEvent(DeferredBookMove, 1);
12765     }
12766 }
12767
12768 void
12769 TrainingEvent()
12770 {
12771     if (gameMode == Training) {
12772       SetTrainingModeOff();
12773       gameMode = PlayFromGameFile;
12774       DisplayMessage("", _("Training mode off"));
12775     } else {
12776       gameMode = Training;
12777       animateTraining = appData.animate;
12778
12779       /* make sure we are not already at the end of the game */
12780       if (currentMove < forwardMostMove) {
12781         SetTrainingModeOn();
12782         DisplayMessage("", _("Training mode on"));
12783       } else {
12784         gameMode = PlayFromGameFile;
12785         DisplayError(_("Already at end of game"), 0);
12786       }
12787     }
12788     ModeHighlight();
12789 }
12790
12791 void
12792 IcsClientEvent()
12793 {
12794     if (!appData.icsActive) return;
12795     switch (gameMode) {
12796       case IcsPlayingWhite:
12797       case IcsPlayingBlack:
12798       case IcsObserving:
12799       case IcsIdle:
12800       case BeginningOfGame:
12801       case IcsExamining:
12802         return;
12803
12804       case EditGame:
12805         break;
12806
12807       case EditPosition:
12808         EditPositionDone(TRUE);
12809         break;
12810
12811       case AnalyzeMode:
12812       case AnalyzeFile:
12813         ExitAnalyzeMode();
12814         break;
12815
12816       default:
12817         EditGameEvent();
12818         break;
12819     }
12820
12821     gameMode = IcsIdle;
12822     ModeHighlight();
12823     return;
12824 }
12825
12826
12827 void
12828 EditGameEvent()
12829 {
12830     int i;
12831
12832     switch (gameMode) {
12833       case Training:
12834         SetTrainingModeOff();
12835         break;
12836       case MachinePlaysWhite:
12837       case MachinePlaysBlack:
12838       case BeginningOfGame:
12839         SendToProgram("force\n", &first);
12840         SetUserThinkingEnables();
12841         break;
12842       case PlayFromGameFile:
12843         (void) StopLoadGameTimer();
12844         if (gameFileFP != NULL) {
12845             gameFileFP = NULL;
12846         }
12847         break;
12848       case EditPosition:
12849         EditPositionDone(TRUE);
12850         break;
12851       case AnalyzeMode:
12852       case AnalyzeFile:
12853         ExitAnalyzeMode();
12854         SendToProgram("force\n", &first);
12855         break;
12856       case TwoMachinesPlay:
12857         GameEnds(EndOfFile, NULL, GE_PLAYER);
12858         ResurrectChessProgram();
12859         SetUserThinkingEnables();
12860         break;
12861       case EndOfGame:
12862         ResurrectChessProgram();
12863         break;
12864       case IcsPlayingBlack:
12865       case IcsPlayingWhite:
12866         DisplayError(_("Warning: You are still playing a game"), 0);
12867         break;
12868       case IcsObserving:
12869         DisplayError(_("Warning: You are still observing a game"), 0);
12870         break;
12871       case IcsExamining:
12872         DisplayError(_("Warning: You are still examining a game"), 0);
12873         break;
12874       case IcsIdle:
12875         break;
12876       case EditGame:
12877       default:
12878         return;
12879     }
12880
12881     pausing = FALSE;
12882     StopClocks();
12883     first.offeredDraw = second.offeredDraw = 0;
12884
12885     if (gameMode == PlayFromGameFile) {
12886         whiteTimeRemaining = timeRemaining[0][currentMove];
12887         blackTimeRemaining = timeRemaining[1][currentMove];
12888         DisplayTitle("");
12889     }
12890
12891     if (gameMode == MachinePlaysWhite ||
12892         gameMode == MachinePlaysBlack ||
12893         gameMode == TwoMachinesPlay ||
12894         gameMode == EndOfGame) {
12895         i = forwardMostMove;
12896         while (i > currentMove) {
12897             SendToProgram("undo\n", &first);
12898             i--;
12899         }
12900         whiteTimeRemaining = timeRemaining[0][currentMove];
12901         blackTimeRemaining = timeRemaining[1][currentMove];
12902         DisplayBothClocks();
12903         if (whiteFlag || blackFlag) {
12904             whiteFlag = blackFlag = 0;
12905         }
12906         DisplayTitle("");
12907     }
12908
12909     gameMode = EditGame;
12910     ModeHighlight();
12911     SetGameInfo();
12912 }
12913
12914
12915 void
12916 EditPositionEvent()
12917 {
12918     if (gameMode == EditPosition) {
12919         EditGameEvent();
12920         return;
12921     }
12922
12923     EditGameEvent();
12924     if (gameMode != EditGame) return;
12925
12926     gameMode = EditPosition;
12927     ModeHighlight();
12928     SetGameInfo();
12929     if (currentMove > 0)
12930       CopyBoard(boards[0], boards[currentMove]);
12931
12932     blackPlaysFirst = !WhiteOnMove(currentMove);
12933     ResetClocks();
12934     currentMove = forwardMostMove = backwardMostMove = 0;
12935     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12936     DisplayMove(-1);
12937 }
12938
12939 void
12940 ExitAnalyzeMode()
12941 {
12942     /* [DM] icsEngineAnalyze - possible call from other functions */
12943     if (appData.icsEngineAnalyze) {
12944         appData.icsEngineAnalyze = FALSE;
12945
12946         DisplayMessage("",_("Close ICS engine analyze..."));
12947     }
12948     if (first.analysisSupport && first.analyzing) {
12949       SendToProgram("exit\n", &first);
12950       first.analyzing = FALSE;
12951     }
12952     thinkOutput[0] = NULLCHAR;
12953 }
12954
12955 void
12956 EditPositionDone(Boolean fakeRights)
12957 {
12958     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12959
12960     startedFromSetupPosition = TRUE;
12961     InitChessProgram(&first, FALSE);
12962     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12963       boards[0][EP_STATUS] = EP_NONE;
12964       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12965     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12966         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12967         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12968       } else boards[0][CASTLING][2] = NoRights;
12969     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12970         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12971         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12972       } else boards[0][CASTLING][5] = NoRights;
12973     }
12974     SendToProgram("force\n", &first);
12975     if (blackPlaysFirst) {
12976         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12977         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12978         currentMove = forwardMostMove = backwardMostMove = 1;
12979         CopyBoard(boards[1], boards[0]);
12980     } else {
12981         currentMove = forwardMostMove = backwardMostMove = 0;
12982     }
12983     SendBoard(&first, forwardMostMove);
12984     if (appData.debugMode) {
12985         fprintf(debugFP, "EditPosDone\n");
12986     }
12987     DisplayTitle("");
12988     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12989     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12990     gameMode = EditGame;
12991     ModeHighlight();
12992     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12993     ClearHighlights(); /* [AS] */
12994 }
12995
12996 /* Pause for `ms' milliseconds */
12997 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12998 void
12999 TimeDelay(ms)
13000      long ms;
13001 {
13002     TimeMark m1, m2;
13003
13004     GetTimeMark(&m1);
13005     do {
13006         GetTimeMark(&m2);
13007     } while (SubtractTimeMarks(&m2, &m1) < ms);
13008 }
13009
13010 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13011 void
13012 SendMultiLineToICS(buf)
13013      char *buf;
13014 {
13015     char temp[MSG_SIZ+1], *p;
13016     int len;
13017
13018     len = strlen(buf);
13019     if (len > MSG_SIZ)
13020       len = MSG_SIZ;
13021
13022     strncpy(temp, buf, len);
13023     temp[len] = 0;
13024
13025     p = temp;
13026     while (*p) {
13027         if (*p == '\n' || *p == '\r')
13028           *p = ' ';
13029         ++p;
13030     }
13031
13032     strcat(temp, "\n");
13033     SendToICS(temp);
13034     SendToPlayer(temp, strlen(temp));
13035 }
13036
13037 void
13038 SetWhiteToPlayEvent()
13039 {
13040     if (gameMode == EditPosition) {
13041         blackPlaysFirst = FALSE;
13042         DisplayBothClocks();    /* works because currentMove is 0 */
13043     } else if (gameMode == IcsExamining) {
13044         SendToICS(ics_prefix);
13045         SendToICS("tomove white\n");
13046     }
13047 }
13048
13049 void
13050 SetBlackToPlayEvent()
13051 {
13052     if (gameMode == EditPosition) {
13053         blackPlaysFirst = TRUE;
13054         currentMove = 1;        /* kludge */
13055         DisplayBothClocks();
13056         currentMove = 0;
13057     } else if (gameMode == IcsExamining) {
13058         SendToICS(ics_prefix);
13059         SendToICS("tomove black\n");
13060     }
13061 }
13062
13063 void
13064 EditPositionMenuEvent(selection, x, y)
13065      ChessSquare selection;
13066      int x, y;
13067 {
13068     char buf[MSG_SIZ];
13069     ChessSquare piece = boards[0][y][x];
13070
13071     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13072
13073     switch (selection) {
13074       case ClearBoard:
13075         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13076             SendToICS(ics_prefix);
13077             SendToICS("bsetup clear\n");
13078         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13079             SendToICS(ics_prefix);
13080             SendToICS("clearboard\n");
13081         } else {
13082             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13083                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13084                 for (y = 0; y < BOARD_HEIGHT; y++) {
13085                     if (gameMode == IcsExamining) {
13086                         if (boards[currentMove][y][x] != EmptySquare) {
13087                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13088                                     AAA + x, ONE + y);
13089                             SendToICS(buf);
13090                         }
13091                     } else {
13092                         boards[0][y][x] = p;
13093                     }
13094                 }
13095             }
13096         }
13097         if (gameMode == EditPosition) {
13098             DrawPosition(FALSE, boards[0]);
13099         }
13100         break;
13101
13102       case WhitePlay:
13103         SetWhiteToPlayEvent();
13104         break;
13105
13106       case BlackPlay:
13107         SetBlackToPlayEvent();
13108         break;
13109
13110       case EmptySquare:
13111         if (gameMode == IcsExamining) {
13112             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13113             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13114             SendToICS(buf);
13115         } else {
13116             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13117                 if(x == BOARD_LEFT-2) {
13118                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13119                     boards[0][y][1] = 0;
13120                 } else
13121                 if(x == BOARD_RGHT+1) {
13122                     if(y >= gameInfo.holdingsSize) break;
13123                     boards[0][y][BOARD_WIDTH-2] = 0;
13124                 } else break;
13125             }
13126             boards[0][y][x] = EmptySquare;
13127             DrawPosition(FALSE, boards[0]);
13128         }
13129         break;
13130
13131       case PromotePiece:
13132         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13133            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13134             selection = (ChessSquare) (PROMOTED piece);
13135         } else if(piece == EmptySquare) selection = WhiteSilver;
13136         else selection = (ChessSquare)((int)piece - 1);
13137         goto defaultlabel;
13138
13139       case DemotePiece:
13140         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13141            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13142             selection = (ChessSquare) (DEMOTED piece);
13143         } else if(piece == EmptySquare) selection = BlackSilver;
13144         else selection = (ChessSquare)((int)piece + 1);
13145         goto defaultlabel;
13146
13147       case WhiteQueen:
13148       case BlackQueen:
13149         if(gameInfo.variant == VariantShatranj ||
13150            gameInfo.variant == VariantXiangqi  ||
13151            gameInfo.variant == VariantCourier  ||
13152            gameInfo.variant == VariantMakruk     )
13153             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13154         goto defaultlabel;
13155
13156       case WhiteKing:
13157       case BlackKing:
13158         if(gameInfo.variant == VariantXiangqi)
13159             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13160         if(gameInfo.variant == VariantKnightmate)
13161             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13162       default:
13163         defaultlabel:
13164         if (gameMode == IcsExamining) {
13165             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13166             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13167                      PieceToChar(selection), AAA + x, ONE + y);
13168             SendToICS(buf);
13169         } else {
13170             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13171                 int n;
13172                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13173                     n = PieceToNumber(selection - BlackPawn);
13174                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13175                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13176                     boards[0][BOARD_HEIGHT-1-n][1]++;
13177                 } else
13178                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13179                     n = PieceToNumber(selection);
13180                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13181                     boards[0][n][BOARD_WIDTH-1] = selection;
13182                     boards[0][n][BOARD_WIDTH-2]++;
13183                 }
13184             } else
13185             boards[0][y][x] = selection;
13186             DrawPosition(TRUE, boards[0]);
13187         }
13188         break;
13189     }
13190 }
13191
13192
13193 void
13194 DropMenuEvent(selection, x, y)
13195      ChessSquare selection;
13196      int x, y;
13197 {
13198     ChessMove moveType;
13199
13200     switch (gameMode) {
13201       case IcsPlayingWhite:
13202       case MachinePlaysBlack:
13203         if (!WhiteOnMove(currentMove)) {
13204             DisplayMoveError(_("It is Black's turn"));
13205             return;
13206         }
13207         moveType = WhiteDrop;
13208         break;
13209       case IcsPlayingBlack:
13210       case MachinePlaysWhite:
13211         if (WhiteOnMove(currentMove)) {
13212             DisplayMoveError(_("It is White's turn"));
13213             return;
13214         }
13215         moveType = BlackDrop;
13216         break;
13217       case EditGame:
13218         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13219         break;
13220       default:
13221         return;
13222     }
13223
13224     if (moveType == BlackDrop && selection < BlackPawn) {
13225       selection = (ChessSquare) ((int) selection
13226                                  + (int) BlackPawn - (int) WhitePawn);
13227     }
13228     if (boards[currentMove][y][x] != EmptySquare) {
13229         DisplayMoveError(_("That square is occupied"));
13230         return;
13231     }
13232
13233     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13234 }
13235
13236 void
13237 AcceptEvent()
13238 {
13239     /* Accept a pending offer of any kind from opponent */
13240
13241     if (appData.icsActive) {
13242         SendToICS(ics_prefix);
13243         SendToICS("accept\n");
13244     } else if (cmailMsgLoaded) {
13245         if (currentMove == cmailOldMove &&
13246             commentList[cmailOldMove] != NULL &&
13247             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13248                    "Black offers a draw" : "White offers a draw")) {
13249             TruncateGame();
13250             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13251             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13252         } else {
13253             DisplayError(_("There is no pending offer on this move"), 0);
13254             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13255         }
13256     } else {
13257         /* Not used for offers from chess program */
13258     }
13259 }
13260
13261 void
13262 DeclineEvent()
13263 {
13264     /* Decline a pending offer of any kind from opponent */
13265
13266     if (appData.icsActive) {
13267         SendToICS(ics_prefix);
13268         SendToICS("decline\n");
13269     } else if (cmailMsgLoaded) {
13270         if (currentMove == cmailOldMove &&
13271             commentList[cmailOldMove] != NULL &&
13272             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13273                    "Black offers a draw" : "White offers a draw")) {
13274 #ifdef NOTDEF
13275             AppendComment(cmailOldMove, "Draw declined", TRUE);
13276             DisplayComment(cmailOldMove - 1, "Draw declined");
13277 #endif /*NOTDEF*/
13278         } else {
13279             DisplayError(_("There is no pending offer on this move"), 0);
13280         }
13281     } else {
13282         /* Not used for offers from chess program */
13283     }
13284 }
13285
13286 void
13287 RematchEvent()
13288 {
13289     /* Issue ICS rematch command */
13290     if (appData.icsActive) {
13291         SendToICS(ics_prefix);
13292         SendToICS("rematch\n");
13293     }
13294 }
13295
13296 void
13297 CallFlagEvent()
13298 {
13299     /* Call your opponent's flag (claim a win on time) */
13300     if (appData.icsActive) {
13301         SendToICS(ics_prefix);
13302         SendToICS("flag\n");
13303     } else {
13304         switch (gameMode) {
13305           default:
13306             return;
13307           case MachinePlaysWhite:
13308             if (whiteFlag) {
13309                 if (blackFlag)
13310                   GameEnds(GameIsDrawn, "Both players ran out of time",
13311                            GE_PLAYER);
13312                 else
13313                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13314             } else {
13315                 DisplayError(_("Your opponent is not out of time"), 0);
13316             }
13317             break;
13318           case MachinePlaysBlack:
13319             if (blackFlag) {
13320                 if (whiteFlag)
13321                   GameEnds(GameIsDrawn, "Both players ran out of time",
13322                            GE_PLAYER);
13323                 else
13324                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13325             } else {
13326                 DisplayError(_("Your opponent is not out of time"), 0);
13327             }
13328             break;
13329         }
13330     }
13331 }
13332
13333 void
13334 ClockClick(int which)
13335 {       // [HGM] code moved to back-end from winboard.c
13336         if(which) { // black clock
13337           if (gameMode == EditPosition || gameMode == IcsExamining) {
13338             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13339             SetBlackToPlayEvent();
13340           } else if (gameMode == EditGame || shiftKey) {
13341             AdjustClock(which, -1);
13342           } else if (gameMode == IcsPlayingWhite ||
13343                      gameMode == MachinePlaysBlack) {
13344             CallFlagEvent();
13345           }
13346         } else { // white clock
13347           if (gameMode == EditPosition || gameMode == IcsExamining) {
13348             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13349             SetWhiteToPlayEvent();
13350           } else if (gameMode == EditGame || shiftKey) {
13351             AdjustClock(which, -1);
13352           } else if (gameMode == IcsPlayingBlack ||
13353                    gameMode == MachinePlaysWhite) {
13354             CallFlagEvent();
13355           }
13356         }
13357 }
13358
13359 void
13360 DrawEvent()
13361 {
13362     /* Offer draw or accept pending draw offer from opponent */
13363
13364     if (appData.icsActive) {
13365         /* Note: tournament rules require draw offers to be
13366            made after you make your move but before you punch
13367            your clock.  Currently ICS doesn't let you do that;
13368            instead, you immediately punch your clock after making
13369            a move, but you can offer a draw at any time. */
13370
13371         SendToICS(ics_prefix);
13372         SendToICS("draw\n");
13373         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13374     } else if (cmailMsgLoaded) {
13375         if (currentMove == cmailOldMove &&
13376             commentList[cmailOldMove] != NULL &&
13377             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13378                    "Black offers a draw" : "White offers a draw")) {
13379             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13380             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13381         } else if (currentMove == cmailOldMove + 1) {
13382             char *offer = WhiteOnMove(cmailOldMove) ?
13383               "White offers a draw" : "Black offers a draw";
13384             AppendComment(currentMove, offer, TRUE);
13385             DisplayComment(currentMove - 1, offer);
13386             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13387         } else {
13388             DisplayError(_("You must make your move before offering a draw"), 0);
13389             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13390         }
13391     } else if (first.offeredDraw) {
13392         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13393     } else {
13394         if (first.sendDrawOffers) {
13395             SendToProgram("draw\n", &first);
13396             userOfferedDraw = TRUE;
13397         }
13398     }
13399 }
13400
13401 void
13402 AdjournEvent()
13403 {
13404     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13405
13406     if (appData.icsActive) {
13407         SendToICS(ics_prefix);
13408         SendToICS("adjourn\n");
13409     } else {
13410         /* Currently GNU Chess doesn't offer or accept Adjourns */
13411     }
13412 }
13413
13414
13415 void
13416 AbortEvent()
13417 {
13418     /* Offer Abort or accept pending Abort offer from opponent */
13419
13420     if (appData.icsActive) {
13421         SendToICS(ics_prefix);
13422         SendToICS("abort\n");
13423     } else {
13424         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13425     }
13426 }
13427
13428 void
13429 ResignEvent()
13430 {
13431     /* Resign.  You can do this even if it's not your turn. */
13432
13433     if (appData.icsActive) {
13434         SendToICS(ics_prefix);
13435         SendToICS("resign\n");
13436     } else {
13437         switch (gameMode) {
13438           case MachinePlaysWhite:
13439             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13440             break;
13441           case MachinePlaysBlack:
13442             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13443             break;
13444           case EditGame:
13445             if (cmailMsgLoaded) {
13446                 TruncateGame();
13447                 if (WhiteOnMove(cmailOldMove)) {
13448                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13449                 } else {
13450                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13451                 }
13452                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13453             }
13454             break;
13455           default:
13456             break;
13457         }
13458     }
13459 }
13460
13461
13462 void
13463 StopObservingEvent()
13464 {
13465     /* Stop observing current games */
13466     SendToICS(ics_prefix);
13467     SendToICS("unobserve\n");
13468 }
13469
13470 void
13471 StopExaminingEvent()
13472 {
13473     /* Stop observing current game */
13474     SendToICS(ics_prefix);
13475     SendToICS("unexamine\n");
13476 }
13477
13478 void
13479 ForwardInner(target)
13480      int target;
13481 {
13482     int limit;
13483
13484     if (appData.debugMode)
13485         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13486                 target, currentMove, forwardMostMove);
13487
13488     if (gameMode == EditPosition)
13489       return;
13490
13491     if (gameMode == PlayFromGameFile && !pausing)
13492       PauseEvent();
13493
13494     if (gameMode == IcsExamining && pausing)
13495       limit = pauseExamForwardMostMove;
13496     else
13497       limit = forwardMostMove;
13498
13499     if (target > limit) target = limit;
13500
13501     if (target > 0 && moveList[target - 1][0]) {
13502         int fromX, fromY, toX, toY;
13503         toX = moveList[target - 1][2] - AAA;
13504         toY = moveList[target - 1][3] - ONE;
13505         if (moveList[target - 1][1] == '@') {
13506             if (appData.highlightLastMove) {
13507                 SetHighlights(-1, -1, toX, toY);
13508             }
13509         } else {
13510             fromX = moveList[target - 1][0] - AAA;
13511             fromY = moveList[target - 1][1] - ONE;
13512             if (target == currentMove + 1) {
13513                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13514             }
13515             if (appData.highlightLastMove) {
13516                 SetHighlights(fromX, fromY, toX, toY);
13517             }
13518         }
13519     }
13520     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13521         gameMode == Training || gameMode == PlayFromGameFile ||
13522         gameMode == AnalyzeFile) {
13523         while (currentMove < target) {
13524             SendMoveToProgram(currentMove++, &first);
13525         }
13526     } else {
13527         currentMove = target;
13528     }
13529
13530     if (gameMode == EditGame || gameMode == EndOfGame) {
13531         whiteTimeRemaining = timeRemaining[0][currentMove];
13532         blackTimeRemaining = timeRemaining[1][currentMove];
13533     }
13534     DisplayBothClocks();
13535     DisplayMove(currentMove - 1);
13536     DrawPosition(FALSE, boards[currentMove]);
13537     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13538     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13539         DisplayComment(currentMove - 1, commentList[currentMove]);
13540     }
13541     DisplayBook(currentMove);
13542 }
13543
13544
13545 void
13546 ForwardEvent()
13547 {
13548     if (gameMode == IcsExamining && !pausing) {
13549         SendToICS(ics_prefix);
13550         SendToICS("forward\n");
13551     } else {
13552         ForwardInner(currentMove + 1);
13553     }
13554 }
13555
13556 void
13557 ToEndEvent()
13558 {
13559     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13560         /* to optimze, we temporarily turn off analysis mode while we feed
13561          * the remaining moves to the engine. Otherwise we get analysis output
13562          * after each move.
13563          */
13564         if (first.analysisSupport) {
13565           SendToProgram("exit\nforce\n", &first);
13566           first.analyzing = FALSE;
13567         }
13568     }
13569
13570     if (gameMode == IcsExamining && !pausing) {
13571         SendToICS(ics_prefix);
13572         SendToICS("forward 999999\n");
13573     } else {
13574         ForwardInner(forwardMostMove);
13575     }
13576
13577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13578         /* we have fed all the moves, so reactivate analysis mode */
13579         SendToProgram("analyze\n", &first);
13580         first.analyzing = TRUE;
13581         /*first.maybeThinking = TRUE;*/
13582         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13583     }
13584 }
13585
13586 void
13587 BackwardInner(target)
13588      int target;
13589 {
13590     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13591
13592     if (appData.debugMode)
13593         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13594                 target, currentMove, forwardMostMove);
13595
13596     if (gameMode == EditPosition) return;
13597     if (currentMove <= backwardMostMove) {
13598         ClearHighlights();
13599         DrawPosition(full_redraw, boards[currentMove]);
13600         return;
13601     }
13602     if (gameMode == PlayFromGameFile && !pausing)
13603       PauseEvent();
13604
13605     if (moveList[target][0]) {
13606         int fromX, fromY, toX, toY;
13607         toX = moveList[target][2] - AAA;
13608         toY = moveList[target][3] - ONE;
13609         if (moveList[target][1] == '@') {
13610             if (appData.highlightLastMove) {
13611                 SetHighlights(-1, -1, toX, toY);
13612             }
13613         } else {
13614             fromX = moveList[target][0] - AAA;
13615             fromY = moveList[target][1] - ONE;
13616             if (target == currentMove - 1) {
13617                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13618             }
13619             if (appData.highlightLastMove) {
13620                 SetHighlights(fromX, fromY, toX, toY);
13621             }
13622         }
13623     }
13624     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13625         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13626         while (currentMove > target) {
13627             SendToProgram("undo\n", &first);
13628             currentMove--;
13629         }
13630     } else {
13631         currentMove = target;
13632     }
13633
13634     if (gameMode == EditGame || gameMode == EndOfGame) {
13635         whiteTimeRemaining = timeRemaining[0][currentMove];
13636         blackTimeRemaining = timeRemaining[1][currentMove];
13637     }
13638     DisplayBothClocks();
13639     DisplayMove(currentMove - 1);
13640     DrawPosition(full_redraw, boards[currentMove]);
13641     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13642     // [HGM] PV info: routine tests if comment empty
13643     DisplayComment(currentMove - 1, commentList[currentMove]);
13644     DisplayBook(currentMove);
13645 }
13646
13647 void
13648 BackwardEvent()
13649 {
13650     if (gameMode == IcsExamining && !pausing) {
13651         SendToICS(ics_prefix);
13652         SendToICS("backward\n");
13653     } else {
13654         BackwardInner(currentMove - 1);
13655     }
13656 }
13657
13658 void
13659 ToStartEvent()
13660 {
13661     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13662         /* to optimize, we temporarily turn off analysis mode while we undo
13663          * all the moves. Otherwise we get analysis output after each undo.
13664          */
13665         if (first.analysisSupport) {
13666           SendToProgram("exit\nforce\n", &first);
13667           first.analyzing = FALSE;
13668         }
13669     }
13670
13671     if (gameMode == IcsExamining && !pausing) {
13672         SendToICS(ics_prefix);
13673         SendToICS("backward 999999\n");
13674     } else {
13675         BackwardInner(backwardMostMove);
13676     }
13677
13678     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13679         /* we have fed all the moves, so reactivate analysis mode */
13680         SendToProgram("analyze\n", &first);
13681         first.analyzing = TRUE;
13682         /*first.maybeThinking = TRUE;*/
13683         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13684     }
13685 }
13686
13687 void
13688 ToNrEvent(int to)
13689 {
13690   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13691   if (to >= forwardMostMove) to = forwardMostMove;
13692   if (to <= backwardMostMove) to = backwardMostMove;
13693   if (to < currentMove) {
13694     BackwardInner(to);
13695   } else {
13696     ForwardInner(to);
13697   }
13698 }
13699
13700 void
13701 RevertEvent(Boolean annotate)
13702 {
13703     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13704         return;
13705     }
13706     if (gameMode != IcsExamining) {
13707         DisplayError(_("You are not examining a game"), 0);
13708         return;
13709     }
13710     if (pausing) {
13711         DisplayError(_("You can't revert while pausing"), 0);
13712         return;
13713     }
13714     SendToICS(ics_prefix);
13715     SendToICS("revert\n");
13716 }
13717
13718 void
13719 RetractMoveEvent()
13720 {
13721     switch (gameMode) {
13722       case MachinePlaysWhite:
13723       case MachinePlaysBlack:
13724         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13725             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13726             return;
13727         }
13728         if (forwardMostMove < 2) return;
13729         currentMove = forwardMostMove = forwardMostMove - 2;
13730         whiteTimeRemaining = timeRemaining[0][currentMove];
13731         blackTimeRemaining = timeRemaining[1][currentMove];
13732         DisplayBothClocks();
13733         DisplayMove(currentMove - 1);
13734         ClearHighlights();/*!! could figure this out*/
13735         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13736         SendToProgram("remove\n", &first);
13737         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13738         break;
13739
13740       case BeginningOfGame:
13741       default:
13742         break;
13743
13744       case IcsPlayingWhite:
13745       case IcsPlayingBlack:
13746         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13747             SendToICS(ics_prefix);
13748             SendToICS("takeback 2\n");
13749         } else {
13750             SendToICS(ics_prefix);
13751             SendToICS("takeback 1\n");
13752         }
13753         break;
13754     }
13755 }
13756
13757 void
13758 MoveNowEvent()
13759 {
13760     ChessProgramState *cps;
13761
13762     switch (gameMode) {
13763       case MachinePlaysWhite:
13764         if (!WhiteOnMove(forwardMostMove)) {
13765             DisplayError(_("It is your turn"), 0);
13766             return;
13767         }
13768         cps = &first;
13769         break;
13770       case MachinePlaysBlack:
13771         if (WhiteOnMove(forwardMostMove)) {
13772             DisplayError(_("It is your turn"), 0);
13773             return;
13774         }
13775         cps = &first;
13776         break;
13777       case TwoMachinesPlay:
13778         if (WhiteOnMove(forwardMostMove) ==
13779             (first.twoMachinesColor[0] == 'w')) {
13780             cps = &first;
13781         } else {
13782             cps = &second;
13783         }
13784         break;
13785       case BeginningOfGame:
13786       default:
13787         return;
13788     }
13789     SendToProgram("?\n", cps);
13790 }
13791
13792 void
13793 TruncateGameEvent()
13794 {
13795     EditGameEvent();
13796     if (gameMode != EditGame) return;
13797     TruncateGame();
13798 }
13799
13800 void
13801 TruncateGame()
13802 {
13803     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13804     if (forwardMostMove > currentMove) {
13805         if (gameInfo.resultDetails != NULL) {
13806             free(gameInfo.resultDetails);
13807             gameInfo.resultDetails = NULL;
13808             gameInfo.result = GameUnfinished;
13809         }
13810         forwardMostMove = currentMove;
13811         HistorySet(parseList, backwardMostMove, forwardMostMove,
13812                    currentMove-1);
13813     }
13814 }
13815
13816 void
13817 HintEvent()
13818 {
13819     if (appData.noChessProgram) return;
13820     switch (gameMode) {
13821       case MachinePlaysWhite:
13822         if (WhiteOnMove(forwardMostMove)) {
13823             DisplayError(_("Wait until your turn"), 0);
13824             return;
13825         }
13826         break;
13827       case BeginningOfGame:
13828       case MachinePlaysBlack:
13829         if (!WhiteOnMove(forwardMostMove)) {
13830             DisplayError(_("Wait until your turn"), 0);
13831             return;
13832         }
13833         break;
13834       default:
13835         DisplayError(_("No hint available"), 0);
13836         return;
13837     }
13838     SendToProgram("hint\n", &first);
13839     hintRequested = TRUE;
13840 }
13841
13842 void
13843 BookEvent()
13844 {
13845     if (appData.noChessProgram) return;
13846     switch (gameMode) {
13847       case MachinePlaysWhite:
13848         if (WhiteOnMove(forwardMostMove)) {
13849             DisplayError(_("Wait until your turn"), 0);
13850             return;
13851         }
13852         break;
13853       case BeginningOfGame:
13854       case MachinePlaysBlack:
13855         if (!WhiteOnMove(forwardMostMove)) {
13856             DisplayError(_("Wait until your turn"), 0);
13857             return;
13858         }
13859         break;
13860       case EditPosition:
13861         EditPositionDone(TRUE);
13862         break;
13863       case TwoMachinesPlay:
13864         return;
13865       default:
13866         break;
13867     }
13868     SendToProgram("bk\n", &first);
13869     bookOutput[0] = NULLCHAR;
13870     bookRequested = TRUE;
13871 }
13872
13873 void
13874 AboutGameEvent()
13875 {
13876     char *tags = PGNTags(&gameInfo);
13877     TagsPopUp(tags, CmailMsg());
13878     free(tags);
13879 }
13880
13881 /* end button procedures */
13882
13883 void
13884 PrintPosition(fp, move)
13885      FILE *fp;
13886      int move;
13887 {
13888     int i, j;
13889
13890     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13891         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13892             char c = PieceToChar(boards[move][i][j]);
13893             fputc(c == 'x' ? '.' : c, fp);
13894             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13895         }
13896     }
13897     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13898       fprintf(fp, "white to play\n");
13899     else
13900       fprintf(fp, "black to play\n");
13901 }
13902
13903 void
13904 PrintOpponents(fp)
13905      FILE *fp;
13906 {
13907     if (gameInfo.white != NULL) {
13908         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13909     } else {
13910         fprintf(fp, "\n");
13911     }
13912 }
13913
13914 /* Find last component of program's own name, using some heuristics */
13915 void
13916 TidyProgramName(prog, host, buf)
13917      char *prog, *host, buf[MSG_SIZ];
13918 {
13919     char *p, *q;
13920     int local = (strcmp(host, "localhost") == 0);
13921     while (!local && (p = strchr(prog, ';')) != NULL) {
13922         p++;
13923         while (*p == ' ') p++;
13924         prog = p;
13925     }
13926     if (*prog == '"' || *prog == '\'') {
13927         q = strchr(prog + 1, *prog);
13928     } else {
13929         q = strchr(prog, ' ');
13930     }
13931     if (q == NULL) q = prog + strlen(prog);
13932     p = q;
13933     while (p >= prog && *p != '/' && *p != '\\') p--;
13934     p++;
13935     if(p == prog && *p == '"') p++;
13936     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13937     memcpy(buf, p, q - p);
13938     buf[q - p] = NULLCHAR;
13939     if (!local) {
13940         strcat(buf, "@");
13941         strcat(buf, host);
13942     }
13943 }
13944
13945 char *
13946 TimeControlTagValue()
13947 {
13948     char buf[MSG_SIZ];
13949     if (!appData.clockMode) {
13950       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13951     } else if (movesPerSession > 0) {
13952       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13953     } else if (timeIncrement == 0) {
13954       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13955     } else {
13956       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13957     }
13958     return StrSave(buf);
13959 }
13960
13961 void
13962 SetGameInfo()
13963 {
13964     /* This routine is used only for certain modes */
13965     VariantClass v = gameInfo.variant;
13966     ChessMove r = GameUnfinished;
13967     char *p = NULL;
13968
13969     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13970         r = gameInfo.result;
13971         p = gameInfo.resultDetails;
13972         gameInfo.resultDetails = NULL;
13973     }
13974     ClearGameInfo(&gameInfo);
13975     gameInfo.variant = v;
13976
13977     switch (gameMode) {
13978       case MachinePlaysWhite:
13979         gameInfo.event = StrSave( appData.pgnEventHeader );
13980         gameInfo.site = StrSave(HostName());
13981         gameInfo.date = PGNDate();
13982         gameInfo.round = StrSave("-");
13983         gameInfo.white = StrSave(first.tidy);
13984         gameInfo.black = StrSave(UserName());
13985         gameInfo.timeControl = TimeControlTagValue();
13986         break;
13987
13988       case MachinePlaysBlack:
13989         gameInfo.event = StrSave( appData.pgnEventHeader );
13990         gameInfo.site = StrSave(HostName());
13991         gameInfo.date = PGNDate();
13992         gameInfo.round = StrSave("-");
13993         gameInfo.white = StrSave(UserName());
13994         gameInfo.black = StrSave(first.tidy);
13995         gameInfo.timeControl = TimeControlTagValue();
13996         break;
13997
13998       case TwoMachinesPlay:
13999         gameInfo.event = StrSave( appData.pgnEventHeader );
14000         gameInfo.site = StrSave(HostName());
14001         gameInfo.date = PGNDate();
14002         if (roundNr > 0) {
14003             char buf[MSG_SIZ];
14004             snprintf(buf, MSG_SIZ, "%d", roundNr);
14005             gameInfo.round = StrSave(buf);
14006         } else {
14007             gameInfo.round = StrSave("-");
14008         }
14009         if (first.twoMachinesColor[0] == 'w') {
14010             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14011             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14012         } else {
14013             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14014             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14015         }
14016         gameInfo.timeControl = TimeControlTagValue();
14017         break;
14018
14019       case EditGame:
14020         gameInfo.event = StrSave("Edited game");
14021         gameInfo.site = StrSave(HostName());
14022         gameInfo.date = PGNDate();
14023         gameInfo.round = StrSave("-");
14024         gameInfo.white = StrSave("-");
14025         gameInfo.black = StrSave("-");
14026         gameInfo.result = r;
14027         gameInfo.resultDetails = p;
14028         break;
14029
14030       case EditPosition:
14031         gameInfo.event = StrSave("Edited position");
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       case IcsPlayingWhite:
14040       case IcsPlayingBlack:
14041       case IcsObserving:
14042       case IcsExamining:
14043         break;
14044
14045       case PlayFromGameFile:
14046         gameInfo.event = StrSave("Game from non-PGN file");
14047         gameInfo.site = StrSave(HostName());
14048         gameInfo.date = PGNDate();
14049         gameInfo.round = StrSave("-");
14050         gameInfo.white = StrSave("?");
14051         gameInfo.black = StrSave("?");
14052         break;
14053
14054       default:
14055         break;
14056     }
14057 }
14058
14059 void
14060 ReplaceComment(index, text)
14061      int index;
14062      char *text;
14063 {
14064     int len;
14065     char *p;
14066     float score;
14067
14068     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14069        pvInfoList[index-1].depth == len &&
14070        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14071        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14072     while (*text == '\n') text++;
14073     len = strlen(text);
14074     while (len > 0 && text[len - 1] == '\n') len--;
14075
14076     if (commentList[index] != NULL)
14077       free(commentList[index]);
14078
14079     if (len == 0) {
14080         commentList[index] = NULL;
14081         return;
14082     }
14083   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14084       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14085       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14086     commentList[index] = (char *) malloc(len + 2);
14087     strncpy(commentList[index], text, len);
14088     commentList[index][len] = '\n';
14089     commentList[index][len + 1] = NULLCHAR;
14090   } else {
14091     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14092     char *p;
14093     commentList[index] = (char *) malloc(len + 7);
14094     safeStrCpy(commentList[index], "{\n", 3);
14095     safeStrCpy(commentList[index]+2, text, len+1);
14096     commentList[index][len+2] = NULLCHAR;
14097     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14098     strcat(commentList[index], "\n}\n");
14099   }
14100 }
14101
14102 void
14103 CrushCRs(text)
14104      char *text;
14105 {
14106   char *p = text;
14107   char *q = text;
14108   char ch;
14109
14110   do {
14111     ch = *p++;
14112     if (ch == '\r') continue;
14113     *q++ = ch;
14114   } while (ch != '\0');
14115 }
14116
14117 void
14118 AppendComment(index, text, addBraces)
14119      int index;
14120      char *text;
14121      Boolean addBraces; // [HGM] braces: tells if we should add {}
14122 {
14123     int oldlen, len;
14124     char *old;
14125
14126 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14127     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14128
14129     CrushCRs(text);
14130     while (*text == '\n') text++;
14131     len = strlen(text);
14132     while (len > 0 && text[len - 1] == '\n') len--;
14133
14134     if (len == 0) return;
14135
14136     if (commentList[index] != NULL) {
14137         old = commentList[index];
14138         oldlen = strlen(old);
14139         while(commentList[index][oldlen-1] ==  '\n')
14140           commentList[index][--oldlen] = NULLCHAR;
14141         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14142         safeStrCpy(commentList[index], old, oldlen + len + 6);
14143         free(old);
14144         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14145         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14146           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14147           while (*text == '\n') { text++; len--; }
14148           commentList[index][--oldlen] = NULLCHAR;
14149       }
14150         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14151         else          strcat(commentList[index], "\n");
14152         strcat(commentList[index], text);
14153         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14154         else          strcat(commentList[index], "\n");
14155     } else {
14156         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14157         if(addBraces)
14158           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14159         else commentList[index][0] = NULLCHAR;
14160         strcat(commentList[index], text);
14161         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14162         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14163     }
14164 }
14165
14166 static char * FindStr( char * text, char * sub_text )
14167 {
14168     char * result = strstr( text, sub_text );
14169
14170     if( result != NULL ) {
14171         result += strlen( sub_text );
14172     }
14173
14174     return result;
14175 }
14176
14177 /* [AS] Try to extract PV info from PGN comment */
14178 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14179 char *GetInfoFromComment( int index, char * text )
14180 {
14181     char * sep = text, *p;
14182
14183     if( text != NULL && index > 0 ) {
14184         int score = 0;
14185         int depth = 0;
14186         int time = -1, sec = 0, deci;
14187         char * s_eval = FindStr( text, "[%eval " );
14188         char * s_emt = FindStr( text, "[%emt " );
14189
14190         if( s_eval != NULL || s_emt != NULL ) {
14191             /* New style */
14192             char delim;
14193
14194             if( s_eval != NULL ) {
14195                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14196                     return text;
14197                 }
14198
14199                 if( delim != ']' ) {
14200                     return text;
14201                 }
14202             }
14203
14204             if( s_emt != NULL ) {
14205             }
14206                 return text;
14207         }
14208         else {
14209             /* We expect something like: [+|-]nnn.nn/dd */
14210             int score_lo = 0;
14211
14212             if(*text != '{') return text; // [HGM] braces: must be normal comment
14213
14214             sep = strchr( text, '/' );
14215             if( sep == NULL || sep < (text+4) ) {
14216                 return text;
14217             }
14218
14219             p = text;
14220             if(p[1] == '(') { // comment starts with PV
14221                p = strchr(p, ')'); // locate end of PV
14222                if(p == NULL || sep < p+5) return text;
14223                // at this point we have something like "{(.*) +0.23/6 ..."
14224                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14225                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14226                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14227             }
14228             time = -1; sec = -1; deci = -1;
14229             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14230                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14231                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14232                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14233                 return text;
14234             }
14235
14236             if( score_lo < 0 || score_lo >= 100 ) {
14237                 return text;
14238             }
14239
14240             if(sec >= 0) time = 600*time + 10*sec; else
14241             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14242
14243             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14244
14245             /* [HGM] PV time: now locate end of PV info */
14246             while( *++sep >= '0' && *sep <= '9'); // strip depth
14247             if(time >= 0)
14248             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14249             if(sec >= 0)
14250             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14251             if(deci >= 0)
14252             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14253             while(*sep == ' ') sep++;
14254         }
14255
14256         if( depth <= 0 ) {
14257             return text;
14258         }
14259
14260         if( time < 0 ) {
14261             time = -1;
14262         }
14263
14264         pvInfoList[index-1].depth = depth;
14265         pvInfoList[index-1].score = score;
14266         pvInfoList[index-1].time  = 10*time; // centi-sec
14267         if(*sep == '}') *sep = 0; else *--sep = '{';
14268         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14269     }
14270     return sep;
14271 }
14272
14273 void
14274 SendToProgram(message, cps)
14275      char *message;
14276      ChessProgramState *cps;
14277 {
14278     int count, outCount, error;
14279     char buf[MSG_SIZ];
14280
14281     if (cps->pr == NULL) return;
14282     Attention(cps);
14283
14284     if (appData.debugMode) {
14285         TimeMark now;
14286         GetTimeMark(&now);
14287         fprintf(debugFP, "%ld >%-6s: %s",
14288                 SubtractTimeMarks(&now, &programStartTime),
14289                 cps->which, message);
14290     }
14291
14292     count = strlen(message);
14293     outCount = OutputToProcess(cps->pr, message, count, &error);
14294     if (outCount < count && !exiting
14295                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14296       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14297       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14298         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14299             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14300                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14301                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14302                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14303             } else {
14304                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14305                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14306                 gameInfo.result = res;
14307             }
14308             gameInfo.resultDetails = StrSave(buf);
14309         }
14310         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14311         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14312     }
14313 }
14314
14315 void
14316 ReceiveFromProgram(isr, closure, message, count, error)
14317      InputSourceRef isr;
14318      VOIDSTAR closure;
14319      char *message;
14320      int count;
14321      int error;
14322 {
14323     char *end_str;
14324     char buf[MSG_SIZ];
14325     ChessProgramState *cps = (ChessProgramState *)closure;
14326
14327     if (isr != cps->isr) return; /* Killed intentionally */
14328     if (count <= 0) {
14329         if (count == 0) {
14330             RemoveInputSource(cps->isr);
14331             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14332                     _(cps->which), cps->program);
14333         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14334                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14335                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14336                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14337                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14338                 } else {
14339                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14340                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14341                     gameInfo.result = res;
14342                 }
14343                 gameInfo.resultDetails = StrSave(buf);
14344             }
14345             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14346             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14347         } else {
14348             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14349                     _(cps->which), cps->program);
14350             RemoveInputSource(cps->isr);
14351
14352             /* [AS] Program is misbehaving badly... kill it */
14353             if( count == -2 ) {
14354                 DestroyChildProcess( cps->pr, 9 );
14355                 cps->pr = NoProc;
14356             }
14357
14358             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14359         }
14360         return;
14361     }
14362
14363     if ((end_str = strchr(message, '\r')) != NULL)
14364       *end_str = NULLCHAR;
14365     if ((end_str = strchr(message, '\n')) != NULL)
14366       *end_str = NULLCHAR;
14367
14368     if (appData.debugMode) {
14369         TimeMark now; int print = 1;
14370         char *quote = ""; char c; int i;
14371
14372         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14373                 char start = message[0];
14374                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14375                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14376                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14377                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14378                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14379                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14380                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14381                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14382                    sscanf(message, "hint: %c", &c)!=1 && 
14383                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14384                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14385                     print = (appData.engineComments >= 2);
14386                 }
14387                 message[0] = start; // restore original message
14388         }
14389         if(print) {
14390                 GetTimeMark(&now);
14391                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14392                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14393                         quote,
14394                         message);
14395         }
14396     }
14397
14398     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14399     if (appData.icsEngineAnalyze) {
14400         if (strstr(message, "whisper") != NULL ||
14401              strstr(message, "kibitz") != NULL ||
14402             strstr(message, "tellics") != NULL) return;
14403     }
14404
14405     HandleMachineMove(message, cps);
14406 }
14407
14408
14409 void
14410 SendTimeControl(cps, mps, tc, inc, sd, st)
14411      ChessProgramState *cps;
14412      int mps, inc, sd, st;
14413      long tc;
14414 {
14415     char buf[MSG_SIZ];
14416     int seconds;
14417
14418     if( timeControl_2 > 0 ) {
14419         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14420             tc = timeControl_2;
14421         }
14422     }
14423     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14424     inc /= cps->timeOdds;
14425     st  /= cps->timeOdds;
14426
14427     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14428
14429     if (st > 0) {
14430       /* Set exact time per move, normally using st command */
14431       if (cps->stKludge) {
14432         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14433         seconds = st % 60;
14434         if (seconds == 0) {
14435           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14436         } else {
14437           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14438         }
14439       } else {
14440         snprintf(buf, MSG_SIZ, "st %d\n", st);
14441       }
14442     } else {
14443       /* Set conventional or incremental time control, using level command */
14444       if (seconds == 0) {
14445         /* Note old gnuchess bug -- minutes:seconds used to not work.
14446            Fixed in later versions, but still avoid :seconds
14447            when seconds is 0. */
14448         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14449       } else {
14450         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14451                  seconds, inc/1000.);
14452       }
14453     }
14454     SendToProgram(buf, cps);
14455
14456     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14457     /* Orthogonally, limit search to given depth */
14458     if (sd > 0) {
14459       if (cps->sdKludge) {
14460         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14461       } else {
14462         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14463       }
14464       SendToProgram(buf, cps);
14465     }
14466
14467     if(cps->nps >= 0) { /* [HGM] nps */
14468         if(cps->supportsNPS == FALSE)
14469           cps->nps = -1; // don't use if engine explicitly says not supported!
14470         else {
14471           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14472           SendToProgram(buf, cps);
14473         }
14474     }
14475 }
14476
14477 ChessProgramState *WhitePlayer()
14478 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14479 {
14480     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14481        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14482         return &second;
14483     return &first;
14484 }
14485
14486 void
14487 SendTimeRemaining(cps, machineWhite)
14488      ChessProgramState *cps;
14489      int /*boolean*/ machineWhite;
14490 {
14491     char message[MSG_SIZ];
14492     long time, otime;
14493
14494     /* Note: this routine must be called when the clocks are stopped
14495        or when they have *just* been set or switched; otherwise
14496        it will be off by the time since the current tick started.
14497     */
14498     if (machineWhite) {
14499         time = whiteTimeRemaining / 10;
14500         otime = blackTimeRemaining / 10;
14501     } else {
14502         time = blackTimeRemaining / 10;
14503         otime = whiteTimeRemaining / 10;
14504     }
14505     /* [HGM] translate opponent's time by time-odds factor */
14506     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14507     if (appData.debugMode) {
14508         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14509     }
14510
14511     if (time <= 0) time = 1;
14512     if (otime <= 0) otime = 1;
14513
14514     snprintf(message, MSG_SIZ, "time %ld\n", time);
14515     SendToProgram(message, cps);
14516
14517     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14518     SendToProgram(message, cps);
14519 }
14520
14521 int
14522 BoolFeature(p, name, loc, cps)
14523      char **p;
14524      char *name;
14525      int *loc;
14526      ChessProgramState *cps;
14527 {
14528   char buf[MSG_SIZ];
14529   int len = strlen(name);
14530   int val;
14531
14532   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14533     (*p) += len + 1;
14534     sscanf(*p, "%d", &val);
14535     *loc = (val != 0);
14536     while (**p && **p != ' ')
14537       (*p)++;
14538     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14539     SendToProgram(buf, cps);
14540     return TRUE;
14541   }
14542   return FALSE;
14543 }
14544
14545 int
14546 IntFeature(p, name, loc, cps)
14547      char **p;
14548      char *name;
14549      int *loc;
14550      ChessProgramState *cps;
14551 {
14552   char buf[MSG_SIZ];
14553   int len = strlen(name);
14554   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14555     (*p) += len + 1;
14556     sscanf(*p, "%d", loc);
14557     while (**p && **p != ' ') (*p)++;
14558     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14559     SendToProgram(buf, cps);
14560     return TRUE;
14561   }
14562   return FALSE;
14563 }
14564
14565 int
14566 StringFeature(p, name, loc, cps)
14567      char **p;
14568      char *name;
14569      char loc[];
14570      ChessProgramState *cps;
14571 {
14572   char buf[MSG_SIZ];
14573   int len = strlen(name);
14574   if (strncmp((*p), name, len) == 0
14575       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14576     (*p) += len + 2;
14577     sscanf(*p, "%[^\"]", loc);
14578     while (**p && **p != '\"') (*p)++;
14579     if (**p == '\"') (*p)++;
14580     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14581     SendToProgram(buf, cps);
14582     return TRUE;
14583   }
14584   return FALSE;
14585 }
14586
14587 int
14588 ParseOption(Option *opt, ChessProgramState *cps)
14589 // [HGM] options: process the string that defines an engine option, and determine
14590 // name, type, default value, and allowed value range
14591 {
14592         char *p, *q, buf[MSG_SIZ];
14593         int n, min = (-1)<<31, max = 1<<31, def;
14594
14595         if(p = strstr(opt->name, " -spin ")) {
14596             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14597             if(max < min) max = min; // enforce consistency
14598             if(def < min) def = min;
14599             if(def > max) def = max;
14600             opt->value = def;
14601             opt->min = min;
14602             opt->max = max;
14603             opt->type = Spin;
14604         } else if((p = strstr(opt->name, " -slider "))) {
14605             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14606             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14607             if(max < min) max = min; // enforce consistency
14608             if(def < min) def = min;
14609             if(def > max) def = max;
14610             opt->value = def;
14611             opt->min = min;
14612             opt->max = max;
14613             opt->type = Spin; // Slider;
14614         } else if((p = strstr(opt->name, " -string "))) {
14615             opt->textValue = p+9;
14616             opt->type = TextBox;
14617         } else if((p = strstr(opt->name, " -file "))) {
14618             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14619             opt->textValue = p+7;
14620             opt->type = FileName; // FileName;
14621         } else if((p = strstr(opt->name, " -path "))) {
14622             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14623             opt->textValue = p+7;
14624             opt->type = PathName; // PathName;
14625         } else if(p = strstr(opt->name, " -check ")) {
14626             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14627             opt->value = (def != 0);
14628             opt->type = CheckBox;
14629         } else if(p = strstr(opt->name, " -combo ")) {
14630             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14631             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14632             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14633             opt->value = n = 0;
14634             while(q = StrStr(q, " /// ")) {
14635                 n++; *q = 0;    // count choices, and null-terminate each of them
14636                 q += 5;
14637                 if(*q == '*') { // remember default, which is marked with * prefix
14638                     q++;
14639                     opt->value = n;
14640                 }
14641                 cps->comboList[cps->comboCnt++] = q;
14642             }
14643             cps->comboList[cps->comboCnt++] = NULL;
14644             opt->max = n + 1;
14645             opt->type = ComboBox;
14646         } else if(p = strstr(opt->name, " -button")) {
14647             opt->type = Button;
14648         } else if(p = strstr(opt->name, " -save")) {
14649             opt->type = SaveButton;
14650         } else return FALSE;
14651         *p = 0; // terminate option name
14652         // now look if the command-line options define a setting for this engine option.
14653         if(cps->optionSettings && cps->optionSettings[0])
14654             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14655         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14656           snprintf(buf, MSG_SIZ, "option %s", p);
14657                 if(p = strstr(buf, ",")) *p = 0;
14658                 if(q = strchr(buf, '=')) switch(opt->type) {
14659                     case ComboBox:
14660                         for(n=0; n<opt->max; n++)
14661                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14662                         break;
14663                     case TextBox:
14664                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14665                         break;
14666                     case Spin:
14667                     case CheckBox:
14668                         opt->value = atoi(q+1);
14669                     default:
14670                         break;
14671                 }
14672                 strcat(buf, "\n");
14673                 SendToProgram(buf, cps);
14674         }
14675         return TRUE;
14676 }
14677
14678 void
14679 FeatureDone(cps, val)
14680      ChessProgramState* cps;
14681      int val;
14682 {
14683   DelayedEventCallback cb = GetDelayedEvent();
14684   if ((cb == InitBackEnd3 && cps == &first) ||
14685       (cb == SettingsMenuIfReady && cps == &second) ||
14686       (cb == LoadEngine) ||
14687       (cb == TwoMachinesEventIfReady)) {
14688     CancelDelayedEvent();
14689     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14690   }
14691   cps->initDone = val;
14692 }
14693
14694 /* Parse feature command from engine */
14695 void
14696 ParseFeatures(args, cps)
14697      char* args;
14698      ChessProgramState *cps;
14699 {
14700   char *p = args;
14701   char *q;
14702   int val;
14703   char buf[MSG_SIZ];
14704
14705   for (;;) {
14706     while (*p == ' ') p++;
14707     if (*p == NULLCHAR) return;
14708
14709     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14710     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14711     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14712     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14713     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14714     if (BoolFeature(&p, "reuse", &val, cps)) {
14715       /* Engine can disable reuse, but can't enable it if user said no */
14716       if (!val) cps->reuse = FALSE;
14717       continue;
14718     }
14719     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14720     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14721       if (gameMode == TwoMachinesPlay) {
14722         DisplayTwoMachinesTitle();
14723       } else {
14724         DisplayTitle("");
14725       }
14726       continue;
14727     }
14728     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14729     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14730     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14731     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14732     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14733     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14734     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14735     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14736     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14737     if (IntFeature(&p, "done", &val, cps)) {
14738       FeatureDone(cps, val);
14739       continue;
14740     }
14741     /* Added by Tord: */
14742     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14743     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14744     /* End of additions by Tord */
14745
14746     /* [HGM] added features: */
14747     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14748     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14749     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14750     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14751     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14752     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14753     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14754         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14755           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14756             SendToProgram(buf, cps);
14757             continue;
14758         }
14759         if(cps->nrOptions >= MAX_OPTIONS) {
14760             cps->nrOptions--;
14761             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14762             DisplayError(buf, 0);
14763         }
14764         continue;
14765     }
14766     /* End of additions by HGM */
14767
14768     /* unknown feature: complain and skip */
14769     q = p;
14770     while (*q && *q != '=') q++;
14771     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14772     SendToProgram(buf, cps);
14773     p = q;
14774     if (*p == '=') {
14775       p++;
14776       if (*p == '\"') {
14777         p++;
14778         while (*p && *p != '\"') p++;
14779         if (*p == '\"') p++;
14780       } else {
14781         while (*p && *p != ' ') p++;
14782       }
14783     }
14784   }
14785
14786 }
14787
14788 void
14789 PeriodicUpdatesEvent(newState)
14790      int newState;
14791 {
14792     if (newState == appData.periodicUpdates)
14793       return;
14794
14795     appData.periodicUpdates=newState;
14796
14797     /* Display type changes, so update it now */
14798 //    DisplayAnalysis();
14799
14800     /* Get the ball rolling again... */
14801     if (newState) {
14802         AnalysisPeriodicEvent(1);
14803         StartAnalysisClock();
14804     }
14805 }
14806
14807 void
14808 PonderNextMoveEvent(newState)
14809      int newState;
14810 {
14811     if (newState == appData.ponderNextMove) return;
14812     if (gameMode == EditPosition) EditPositionDone(TRUE);
14813     if (newState) {
14814         SendToProgram("hard\n", &first);
14815         if (gameMode == TwoMachinesPlay) {
14816             SendToProgram("hard\n", &second);
14817         }
14818     } else {
14819         SendToProgram("easy\n", &first);
14820         thinkOutput[0] = NULLCHAR;
14821         if (gameMode == TwoMachinesPlay) {
14822             SendToProgram("easy\n", &second);
14823         }
14824     }
14825     appData.ponderNextMove = newState;
14826 }
14827
14828 void
14829 NewSettingEvent(option, feature, command, value)
14830      char *command;
14831      int option, value, *feature;
14832 {
14833     char buf[MSG_SIZ];
14834
14835     if (gameMode == EditPosition) EditPositionDone(TRUE);
14836     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14837     if(feature == NULL || *feature) SendToProgram(buf, &first);
14838     if (gameMode == TwoMachinesPlay) {
14839         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14840     }
14841 }
14842
14843 void
14844 ShowThinkingEvent()
14845 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14846 {
14847     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14848     int newState = appData.showThinking
14849         // [HGM] thinking: other features now need thinking output as well
14850         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14851
14852     if (oldState == newState) return;
14853     oldState = newState;
14854     if (gameMode == EditPosition) EditPositionDone(TRUE);
14855     if (oldState) {
14856         SendToProgram("post\n", &first);
14857         if (gameMode == TwoMachinesPlay) {
14858             SendToProgram("post\n", &second);
14859         }
14860     } else {
14861         SendToProgram("nopost\n", &first);
14862         thinkOutput[0] = NULLCHAR;
14863         if (gameMode == TwoMachinesPlay) {
14864             SendToProgram("nopost\n", &second);
14865         }
14866     }
14867 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14868 }
14869
14870 void
14871 AskQuestionEvent(title, question, replyPrefix, which)
14872      char *title; char *question; char *replyPrefix; char *which;
14873 {
14874   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14875   if (pr == NoProc) return;
14876   AskQuestion(title, question, replyPrefix, pr);
14877 }
14878
14879 void
14880 TypeInEvent(char firstChar)
14881 {
14882     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14883         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14884         gameMode == AnalyzeMode || gameMode == EditGame || \r
14885         gameMode == EditPosition || gameMode == IcsExamining ||\r
14886         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14887         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14888                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14889                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14890         gameMode == Training) PopUpMoveDialog(firstChar);
14891 }
14892
14893 void
14894 TypeInDoneEvent(char *move)
14895 {
14896         Board board;
14897         int n, fromX, fromY, toX, toY;
14898         char promoChar;
14899         ChessMove moveType;\r
14900
14901         // [HGM] FENedit\r
14902         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14903                 EditPositionPasteFEN(move);\r
14904                 return;\r
14905         }\r
14906         // [HGM] movenum: allow move number to be typed in any mode\r
14907         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14908           ToNrEvent(2*n-1);\r
14909           return;\r
14910         }\r
14911
14912       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14913         gameMode != Training) {\r
14914         DisplayMoveError(_("Displayed move is not current"));\r
14915       } else {\r
14916         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14917           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14918         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14919         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14920           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14921           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14922         } else {\r
14923           DisplayMoveError(_("Could not parse move"));\r
14924         }
14925       }\r
14926 }\r
14927
14928 void
14929 DisplayMove(moveNumber)
14930      int moveNumber;
14931 {
14932     char message[MSG_SIZ];
14933     char res[MSG_SIZ];
14934     char cpThinkOutput[MSG_SIZ];
14935
14936     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14937
14938     if (moveNumber == forwardMostMove - 1 ||
14939         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14940
14941         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14942
14943         if (strchr(cpThinkOutput, '\n')) {
14944             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14945         }
14946     } else {
14947         *cpThinkOutput = NULLCHAR;
14948     }
14949
14950     /* [AS] Hide thinking from human user */
14951     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14952         *cpThinkOutput = NULLCHAR;
14953         if( thinkOutput[0] != NULLCHAR ) {
14954             int i;
14955
14956             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14957                 cpThinkOutput[i] = '.';
14958             }
14959             cpThinkOutput[i] = NULLCHAR;
14960             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14961         }
14962     }
14963
14964     if (moveNumber == forwardMostMove - 1 &&
14965         gameInfo.resultDetails != NULL) {
14966         if (gameInfo.resultDetails[0] == NULLCHAR) {
14967           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14968         } else {
14969           snprintf(res, MSG_SIZ, " {%s} %s",
14970                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14971         }
14972     } else {
14973         res[0] = NULLCHAR;
14974     }
14975
14976     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14977         DisplayMessage(res, cpThinkOutput);
14978     } else {
14979       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14980                 WhiteOnMove(moveNumber) ? " " : ".. ",
14981                 parseList[moveNumber], res);
14982         DisplayMessage(message, cpThinkOutput);
14983     }
14984 }
14985
14986 void
14987 DisplayComment(moveNumber, text)
14988      int moveNumber;
14989      char *text;
14990 {
14991     char title[MSG_SIZ];
14992     char buf[8000]; // comment can be long!
14993     int score, depth;
14994
14995     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14996       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14997     } else {
14998       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14999               WhiteOnMove(moveNumber) ? " " : ".. ",
15000               parseList[moveNumber]);
15001     }
15002     // [HGM] PV info: display PV info together with (or as) comment
15003     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15004       if(text == NULL) text = "";
15005       score = pvInfoList[moveNumber].score;
15006       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15007               depth, (pvInfoList[moveNumber].time+50)/100, text);
15008       text = buf;
15009     }
15010     if (text != NULL && (appData.autoDisplayComment || commentUp))
15011         CommentPopUp(title, text);
15012 }
15013
15014 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15015  * might be busy thinking or pondering.  It can be omitted if your
15016  * gnuchess is configured to stop thinking immediately on any user
15017  * input.  However, that gnuchess feature depends on the FIONREAD
15018  * ioctl, which does not work properly on some flavors of Unix.
15019  */
15020 void
15021 Attention(cps)
15022      ChessProgramState *cps;
15023 {
15024 #if ATTENTION
15025     if (!cps->useSigint) return;
15026     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15027     switch (gameMode) {
15028       case MachinePlaysWhite:
15029       case MachinePlaysBlack:
15030       case TwoMachinesPlay:
15031       case IcsPlayingWhite:
15032       case IcsPlayingBlack:
15033       case AnalyzeMode:
15034       case AnalyzeFile:
15035         /* Skip if we know it isn't thinking */
15036         if (!cps->maybeThinking) return;
15037         if (appData.debugMode)
15038           fprintf(debugFP, "Interrupting %s\n", cps->which);
15039         InterruptChildProcess(cps->pr);
15040         cps->maybeThinking = FALSE;
15041         break;
15042       default:
15043         break;
15044     }
15045 #endif /*ATTENTION*/
15046 }
15047
15048 int
15049 CheckFlags()
15050 {
15051     if (whiteTimeRemaining <= 0) {
15052         if (!whiteFlag) {
15053             whiteFlag = TRUE;
15054             if (appData.icsActive) {
15055                 if (appData.autoCallFlag &&
15056                     gameMode == IcsPlayingBlack && !blackFlag) {
15057                   SendToICS(ics_prefix);
15058                   SendToICS("flag\n");
15059                 }
15060             } else {
15061                 if (blackFlag) {
15062                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15063                 } else {
15064                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15065                     if (appData.autoCallFlag) {
15066                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15067                         return TRUE;
15068                     }
15069                 }
15070             }
15071         }
15072     }
15073     if (blackTimeRemaining <= 0) {
15074         if (!blackFlag) {
15075             blackFlag = TRUE;
15076             if (appData.icsActive) {
15077                 if (appData.autoCallFlag &&
15078                     gameMode == IcsPlayingWhite && !whiteFlag) {
15079                   SendToICS(ics_prefix);
15080                   SendToICS("flag\n");
15081                 }
15082             } else {
15083                 if (whiteFlag) {
15084                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15085                 } else {
15086                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15087                     if (appData.autoCallFlag) {
15088                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15089                         return TRUE;
15090                     }
15091                 }
15092             }
15093         }
15094     }
15095     return FALSE;
15096 }
15097
15098 void
15099 CheckTimeControl()
15100 {
15101     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15102         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15103
15104     /*
15105      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15106      */
15107     if ( !WhiteOnMove(forwardMostMove) ) {
15108         /* White made time control */
15109         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15110         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15111         /* [HGM] time odds: correct new time quota for time odds! */
15112                                             / WhitePlayer()->timeOdds;
15113         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15114     } else {
15115         lastBlack -= blackTimeRemaining;
15116         /* Black made time control */
15117         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15118                                             / WhitePlayer()->other->timeOdds;
15119         lastWhite = whiteTimeRemaining;
15120     }
15121 }
15122
15123 void
15124 DisplayBothClocks()
15125 {
15126     int wom = gameMode == EditPosition ?
15127       !blackPlaysFirst : WhiteOnMove(currentMove);
15128     DisplayWhiteClock(whiteTimeRemaining, wom);
15129     DisplayBlackClock(blackTimeRemaining, !wom);
15130 }
15131
15132
15133 /* Timekeeping seems to be a portability nightmare.  I think everyone
15134    has ftime(), but I'm really not sure, so I'm including some ifdefs
15135    to use other calls if you don't.  Clocks will be less accurate if
15136    you have neither ftime nor gettimeofday.
15137 */
15138
15139 /* VS 2008 requires the #include outside of the function */
15140 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15141 #include <sys/timeb.h>
15142 #endif
15143
15144 /* Get the current time as a TimeMark */
15145 void
15146 GetTimeMark(tm)
15147      TimeMark *tm;
15148 {
15149 #if HAVE_GETTIMEOFDAY
15150
15151     struct timeval timeVal;
15152     struct timezone timeZone;
15153
15154     gettimeofday(&timeVal, &timeZone);
15155     tm->sec = (long) timeVal.tv_sec;
15156     tm->ms = (int) (timeVal.tv_usec / 1000L);
15157
15158 #else /*!HAVE_GETTIMEOFDAY*/
15159 #if HAVE_FTIME
15160
15161 // include <sys/timeb.h> / moved to just above start of function
15162     struct timeb timeB;
15163
15164     ftime(&timeB);
15165     tm->sec = (long) timeB.time;
15166     tm->ms = (int) timeB.millitm;
15167
15168 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15169     tm->sec = (long) time(NULL);
15170     tm->ms = 0;
15171 #endif
15172 #endif
15173 }
15174
15175 /* Return the difference in milliseconds between two
15176    time marks.  We assume the difference will fit in a long!
15177 */
15178 long
15179 SubtractTimeMarks(tm2, tm1)
15180      TimeMark *tm2, *tm1;
15181 {
15182     return 1000L*(tm2->sec - tm1->sec) +
15183            (long) (tm2->ms - tm1->ms);
15184 }
15185
15186
15187 /*
15188  * Code to manage the game clocks.
15189  *
15190  * In tournament play, black starts the clock and then white makes a move.
15191  * We give the human user a slight advantage if he is playing white---the
15192  * clocks don't run until he makes his first move, so it takes zero time.
15193  * Also, we don't account for network lag, so we could get out of sync
15194  * with GNU Chess's clock -- but then, referees are always right.
15195  */
15196
15197 static TimeMark tickStartTM;
15198 static long intendedTickLength;
15199
15200 long
15201 NextTickLength(timeRemaining)
15202      long timeRemaining;
15203 {
15204     long nominalTickLength, nextTickLength;
15205
15206     if (timeRemaining > 0L && timeRemaining <= 10000L)
15207       nominalTickLength = 100L;
15208     else
15209       nominalTickLength = 1000L;
15210     nextTickLength = timeRemaining % nominalTickLength;
15211     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15212
15213     return nextTickLength;
15214 }
15215
15216 /* Adjust clock one minute up or down */
15217 void
15218 AdjustClock(Boolean which, int dir)
15219 {
15220     if(which) blackTimeRemaining += 60000*dir;
15221     else      whiteTimeRemaining += 60000*dir;
15222     DisplayBothClocks();
15223 }
15224
15225 /* Stop clocks and reset to a fresh time control */
15226 void
15227 ResetClocks()
15228 {
15229     (void) StopClockTimer();
15230     if (appData.icsActive) {
15231         whiteTimeRemaining = blackTimeRemaining = 0;
15232     } else if (searchTime) {
15233         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15234         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15235     } else { /* [HGM] correct new time quote for time odds */
15236         whiteTC = blackTC = fullTimeControlString;
15237         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15238         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15239     }
15240     if (whiteFlag || blackFlag) {
15241         DisplayTitle("");
15242         whiteFlag = blackFlag = FALSE;
15243     }
15244     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15245     DisplayBothClocks();
15246 }
15247
15248 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15249
15250 /* Decrement running clock by amount of time that has passed */
15251 void
15252 DecrementClocks()
15253 {
15254     long timeRemaining;
15255     long lastTickLength, fudge;
15256     TimeMark now;
15257
15258     if (!appData.clockMode) return;
15259     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15260
15261     GetTimeMark(&now);
15262
15263     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15264
15265     /* Fudge if we woke up a little too soon */
15266     fudge = intendedTickLength - lastTickLength;
15267     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15268
15269     if (WhiteOnMove(forwardMostMove)) {
15270         if(whiteNPS >= 0) lastTickLength = 0;
15271         timeRemaining = whiteTimeRemaining -= lastTickLength;
15272         if(timeRemaining < 0 && !appData.icsActive) {
15273             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15274             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15275                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15276                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15277             }
15278         }
15279         DisplayWhiteClock(whiteTimeRemaining - fudge,
15280                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15281     } else {
15282         if(blackNPS >= 0) lastTickLength = 0;
15283         timeRemaining = blackTimeRemaining -= lastTickLength;
15284         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15285             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15286             if(suddenDeath) {
15287                 blackStartMove = forwardMostMove;
15288                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15289             }
15290         }
15291         DisplayBlackClock(blackTimeRemaining - fudge,
15292                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15293     }
15294     if (CheckFlags()) return;
15295
15296     tickStartTM = now;
15297     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15298     StartClockTimer(intendedTickLength);
15299
15300     /* if the time remaining has fallen below the alarm threshold, sound the
15301      * alarm. if the alarm has sounded and (due to a takeback or time control
15302      * with increment) the time remaining has increased to a level above the
15303      * threshold, reset the alarm so it can sound again.
15304      */
15305
15306     if (appData.icsActive && appData.icsAlarm) {
15307
15308         /* make sure we are dealing with the user's clock */
15309         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15310                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15311            )) return;
15312
15313         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15314             alarmSounded = FALSE;
15315         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15316             PlayAlarmSound();
15317             alarmSounded = TRUE;
15318         }
15319     }
15320 }
15321
15322
15323 /* A player has just moved, so stop the previously running
15324    clock and (if in clock mode) start the other one.
15325    We redisplay both clocks in case we're in ICS mode, because
15326    ICS gives us an update to both clocks after every move.
15327    Note that this routine is called *after* forwardMostMove
15328    is updated, so the last fractional tick must be subtracted
15329    from the color that is *not* on move now.
15330 */
15331 void
15332 SwitchClocks(int newMoveNr)
15333 {
15334     long lastTickLength;
15335     TimeMark now;
15336     int flagged = FALSE;
15337
15338     GetTimeMark(&now);
15339
15340     if (StopClockTimer() && appData.clockMode) {
15341         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15342         if (!WhiteOnMove(forwardMostMove)) {
15343             if(blackNPS >= 0) lastTickLength = 0;
15344             blackTimeRemaining -= lastTickLength;
15345            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15346 //         if(pvInfoList[forwardMostMove].time == -1)
15347                  pvInfoList[forwardMostMove].time =               // use GUI time
15348                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15349         } else {
15350            if(whiteNPS >= 0) lastTickLength = 0;
15351            whiteTimeRemaining -= lastTickLength;
15352            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15353 //         if(pvInfoList[forwardMostMove].time == -1)
15354                  pvInfoList[forwardMostMove].time =
15355                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15356         }
15357         flagged = CheckFlags();
15358     }
15359     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15360     CheckTimeControl();
15361
15362     if (flagged || !appData.clockMode) return;
15363
15364     switch (gameMode) {
15365       case MachinePlaysBlack:
15366       case MachinePlaysWhite:
15367       case BeginningOfGame:
15368         if (pausing) return;
15369         break;
15370
15371       case EditGame:
15372       case PlayFromGameFile:
15373       case IcsExamining:
15374         return;
15375
15376       default:
15377         break;
15378     }
15379
15380     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15381         if(WhiteOnMove(forwardMostMove))
15382              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15383         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15384     }
15385
15386     tickStartTM = now;
15387     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15388       whiteTimeRemaining : blackTimeRemaining);
15389     StartClockTimer(intendedTickLength);
15390 }
15391
15392
15393 /* Stop both clocks */
15394 void
15395 StopClocks()
15396 {
15397     long lastTickLength;
15398     TimeMark now;
15399
15400     if (!StopClockTimer()) return;
15401     if (!appData.clockMode) return;
15402
15403     GetTimeMark(&now);
15404
15405     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15406     if (WhiteOnMove(forwardMostMove)) {
15407         if(whiteNPS >= 0) lastTickLength = 0;
15408         whiteTimeRemaining -= lastTickLength;
15409         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15410     } else {
15411         if(blackNPS >= 0) lastTickLength = 0;
15412         blackTimeRemaining -= lastTickLength;
15413         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15414     }
15415     CheckFlags();
15416 }
15417
15418 /* Start clock of player on move.  Time may have been reset, so
15419    if clock is already running, stop and restart it. */
15420 void
15421 StartClocks()
15422 {
15423     (void) StopClockTimer(); /* in case it was running already */
15424     DisplayBothClocks();
15425     if (CheckFlags()) return;
15426
15427     if (!appData.clockMode) return;
15428     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15429
15430     GetTimeMark(&tickStartTM);
15431     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15432       whiteTimeRemaining : blackTimeRemaining);
15433
15434    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15435     whiteNPS = blackNPS = -1;
15436     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15437        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15438         whiteNPS = first.nps;
15439     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15440        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15441         blackNPS = first.nps;
15442     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15443         whiteNPS = second.nps;
15444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15445         blackNPS = second.nps;
15446     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15447
15448     StartClockTimer(intendedTickLength);
15449 }
15450
15451 char *
15452 TimeString(ms)
15453      long ms;
15454 {
15455     long second, minute, hour, day;
15456     char *sign = "";
15457     static char buf[32];
15458
15459     if (ms > 0 && ms <= 9900) {
15460       /* convert milliseconds to tenths, rounding up */
15461       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15462
15463       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15464       return buf;
15465     }
15466
15467     /* convert milliseconds to seconds, rounding up */
15468     /* use floating point to avoid strangeness of integer division
15469        with negative dividends on many machines */
15470     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15471
15472     if (second < 0) {
15473         sign = "-";
15474         second = -second;
15475     }
15476
15477     day = second / (60 * 60 * 24);
15478     second = second % (60 * 60 * 24);
15479     hour = second / (60 * 60);
15480     second = second % (60 * 60);
15481     minute = second / 60;
15482     second = second % 60;
15483
15484     if (day > 0)
15485       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15486               sign, day, hour, minute, second);
15487     else if (hour > 0)
15488       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15489     else
15490       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15491
15492     return buf;
15493 }
15494
15495
15496 /*
15497  * This is necessary because some C libraries aren't ANSI C compliant yet.
15498  */
15499 char *
15500 StrStr(string, match)
15501      char *string, *match;
15502 {
15503     int i, length;
15504
15505     length = strlen(match);
15506
15507     for (i = strlen(string) - length; i >= 0; i--, string++)
15508       if (!strncmp(match, string, length))
15509         return string;
15510
15511     return NULL;
15512 }
15513
15514 char *
15515 StrCaseStr(string, match)
15516      char *string, *match;
15517 {
15518     int i, j, length;
15519
15520     length = strlen(match);
15521
15522     for (i = strlen(string) - length; i >= 0; i--, string++) {
15523         for (j = 0; j < length; j++) {
15524             if (ToLower(match[j]) != ToLower(string[j]))
15525               break;
15526         }
15527         if (j == length) return string;
15528     }
15529
15530     return NULL;
15531 }
15532
15533 #ifndef _amigados
15534 int
15535 StrCaseCmp(s1, s2)
15536      char *s1, *s2;
15537 {
15538     char c1, c2;
15539
15540     for (;;) {
15541         c1 = ToLower(*s1++);
15542         c2 = ToLower(*s2++);
15543         if (c1 > c2) return 1;
15544         if (c1 < c2) return -1;
15545         if (c1 == NULLCHAR) return 0;
15546     }
15547 }
15548
15549
15550 int
15551 ToLower(c)
15552      int c;
15553 {
15554     return isupper(c) ? tolower(c) : c;
15555 }
15556
15557
15558 int
15559 ToUpper(c)
15560      int c;
15561 {
15562     return islower(c) ? toupper(c) : c;
15563 }
15564 #endif /* !_amigados    */
15565
15566 char *
15567 StrSave(s)
15568      char *s;
15569 {
15570   char *ret;
15571
15572   if ((ret = (char *) malloc(strlen(s) + 1)))
15573     {
15574       safeStrCpy(ret, s, strlen(s)+1);
15575     }
15576   return ret;
15577 }
15578
15579 char *
15580 StrSavePtr(s, savePtr)
15581      char *s, **savePtr;
15582 {
15583     if (*savePtr) {
15584         free(*savePtr);
15585     }
15586     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15587       safeStrCpy(*savePtr, s, strlen(s)+1);
15588     }
15589     return(*savePtr);
15590 }
15591
15592 char *
15593 PGNDate()
15594 {
15595     time_t clock;
15596     struct tm *tm;
15597     char buf[MSG_SIZ];
15598
15599     clock = time((time_t *)NULL);
15600     tm = localtime(&clock);
15601     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15602             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15603     return StrSave(buf);
15604 }
15605
15606
15607 char *
15608 PositionToFEN(move, overrideCastling)
15609      int move;
15610      char *overrideCastling;
15611 {
15612     int i, j, fromX, fromY, toX, toY;
15613     int whiteToPlay;
15614     char buf[128];
15615     char *p, *q;
15616     int emptycount;
15617     ChessSquare piece;
15618
15619     whiteToPlay = (gameMode == EditPosition) ?
15620       !blackPlaysFirst : (move % 2 == 0);
15621     p = buf;
15622
15623     /* Piece placement data */
15624     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15625         emptycount = 0;
15626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15627             if (boards[move][i][j] == EmptySquare) {
15628                 emptycount++;
15629             } else { ChessSquare piece = boards[move][i][j];
15630                 if (emptycount > 0) {
15631                     if(emptycount<10) /* [HGM] can be >= 10 */
15632                         *p++ = '0' + emptycount;
15633                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15634                     emptycount = 0;
15635                 }
15636                 if(PieceToChar(piece) == '+') {
15637                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15638                     *p++ = '+';
15639                     piece = (ChessSquare)(DEMOTED piece);
15640                 }
15641                 *p++ = PieceToChar(piece);
15642                 if(p[-1] == '~') {
15643                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15644                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15645                     *p++ = '~';
15646                 }
15647             }
15648         }
15649         if (emptycount > 0) {
15650             if(emptycount<10) /* [HGM] can be >= 10 */
15651                 *p++ = '0' + emptycount;
15652             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15653             emptycount = 0;
15654         }
15655         *p++ = '/';
15656     }
15657     *(p - 1) = ' ';
15658
15659     /* [HGM] print Crazyhouse or Shogi holdings */
15660     if( gameInfo.holdingsWidth ) {
15661         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15662         q = p;
15663         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15664             piece = boards[move][i][BOARD_WIDTH-1];
15665             if( piece != EmptySquare )
15666               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15667                   *p++ = PieceToChar(piece);
15668         }
15669         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15670             piece = boards[move][BOARD_HEIGHT-i-1][0];
15671             if( piece != EmptySquare )
15672               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15673                   *p++ = PieceToChar(piece);
15674         }
15675
15676         if( q == p ) *p++ = '-';
15677         *p++ = ']';
15678         *p++ = ' ';
15679     }
15680
15681     /* Active color */
15682     *p++ = whiteToPlay ? 'w' : 'b';
15683     *p++ = ' ';
15684
15685   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15686     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15687   } else {
15688   if(nrCastlingRights) {
15689      q = p;
15690      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15691        /* [HGM] write directly from rights */
15692            if(boards[move][CASTLING][2] != NoRights &&
15693               boards[move][CASTLING][0] != NoRights   )
15694                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15695            if(boards[move][CASTLING][2] != NoRights &&
15696               boards[move][CASTLING][1] != NoRights   )
15697                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15698            if(boards[move][CASTLING][5] != NoRights &&
15699               boards[move][CASTLING][3] != NoRights   )
15700                 *p++ = boards[move][CASTLING][3] + AAA;
15701            if(boards[move][CASTLING][5] != NoRights &&
15702               boards[move][CASTLING][4] != NoRights   )
15703                 *p++ = boards[move][CASTLING][4] + AAA;
15704      } else {
15705
15706         /* [HGM] write true castling rights */
15707         if( nrCastlingRights == 6 ) {
15708             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15709                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15710             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15711                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15712             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15713                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15714             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15715                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15716         }
15717      }
15718      if (q == p) *p++ = '-'; /* No castling rights */
15719      *p++ = ' ';
15720   }
15721
15722   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15723      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15724     /* En passant target square */
15725     if (move > backwardMostMove) {
15726         fromX = moveList[move - 1][0] - AAA;
15727         fromY = moveList[move - 1][1] - ONE;
15728         toX = moveList[move - 1][2] - AAA;
15729         toY = moveList[move - 1][3] - ONE;
15730         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15731             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15732             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15733             fromX == toX) {
15734             /* 2-square pawn move just happened */
15735             *p++ = toX + AAA;
15736             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15737         } else {
15738             *p++ = '-';
15739         }
15740     } else if(move == backwardMostMove) {
15741         // [HGM] perhaps we should always do it like this, and forget the above?
15742         if((signed char)boards[move][EP_STATUS] >= 0) {
15743             *p++ = boards[move][EP_STATUS] + AAA;
15744             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15745         } else {
15746             *p++ = '-';
15747         }
15748     } else {
15749         *p++ = '-';
15750     }
15751     *p++ = ' ';
15752   }
15753   }
15754
15755     /* [HGM] find reversible plies */
15756     {   int i = 0, j=move;
15757
15758         if (appData.debugMode) { int k;
15759             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15760             for(k=backwardMostMove; k<=forwardMostMove; k++)
15761                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15762
15763         }
15764
15765         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15766         if( j == backwardMostMove ) i += initialRulePlies;
15767         sprintf(p, "%d ", i);
15768         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15769     }
15770     /* Fullmove number */
15771     sprintf(p, "%d", (move / 2) + 1);
15772
15773     return StrSave(buf);
15774 }
15775
15776 Boolean
15777 ParseFEN(board, blackPlaysFirst, fen)
15778     Board board;
15779      int *blackPlaysFirst;
15780      char *fen;
15781 {
15782     int i, j;
15783     char *p, c;
15784     int emptycount;
15785     ChessSquare piece;
15786
15787     p = fen;
15788
15789     /* [HGM] by default clear Crazyhouse holdings, if present */
15790     if(gameInfo.holdingsWidth) {
15791        for(i=0; i<BOARD_HEIGHT; i++) {
15792            board[i][0]             = EmptySquare; /* black holdings */
15793            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15794            board[i][1]             = (ChessSquare) 0; /* black counts */
15795            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15796        }
15797     }
15798
15799     /* Piece placement data */
15800     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15801         j = 0;
15802         for (;;) {
15803             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15804                 if (*p == '/') p++;
15805                 emptycount = gameInfo.boardWidth - j;
15806                 while (emptycount--)
15807                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15808                 break;
15809 #if(BOARD_FILES >= 10)
15810             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15811                 p++; emptycount=10;
15812                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15813                 while (emptycount--)
15814                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15815 #endif
15816             } else if (isdigit(*p)) {
15817                 emptycount = *p++ - '0';
15818                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15819                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15820                 while (emptycount--)
15821                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15822             } else if (*p == '+' || isalpha(*p)) {
15823                 if (j >= gameInfo.boardWidth) return FALSE;
15824                 if(*p=='+') {
15825                     piece = CharToPiece(*++p);
15826                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15827                     piece = (ChessSquare) (PROMOTED piece ); p++;
15828                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15829                 } else piece = CharToPiece(*p++);
15830
15831                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15832                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15833                     piece = (ChessSquare) (PROMOTED piece);
15834                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15835                     p++;
15836                 }
15837                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15838             } else {
15839                 return FALSE;
15840             }
15841         }
15842     }
15843     while (*p == '/' || *p == ' ') p++;
15844
15845     /* [HGM] look for Crazyhouse holdings here */
15846     while(*p==' ') p++;
15847     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15848         if(*p == '[') p++;
15849         if(*p == '-' ) p++; /* empty holdings */ else {
15850             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15851             /* if we would allow FEN reading to set board size, we would   */
15852             /* have to add holdings and shift the board read so far here   */
15853             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15854                 p++;
15855                 if((int) piece >= (int) BlackPawn ) {
15856                     i = (int)piece - (int)BlackPawn;
15857                     i = PieceToNumber((ChessSquare)i);
15858                     if( i >= gameInfo.holdingsSize ) return FALSE;
15859                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15860                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15861                 } else {
15862                     i = (int)piece - (int)WhitePawn;
15863                     i = PieceToNumber((ChessSquare)i);
15864                     if( i >= gameInfo.holdingsSize ) return FALSE;
15865                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15866                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15867                 }
15868             }
15869         }
15870         if(*p == ']') p++;
15871     }
15872
15873     while(*p == ' ') p++;
15874
15875     /* Active color */
15876     c = *p++;
15877     if(appData.colorNickNames) {
15878       if( c == appData.colorNickNames[0] ) c = 'w'; else
15879       if( c == appData.colorNickNames[1] ) c = 'b';
15880     }
15881     switch (c) {
15882       case 'w':
15883         *blackPlaysFirst = FALSE;
15884         break;
15885       case 'b':
15886         *blackPlaysFirst = TRUE;
15887         break;
15888       default:
15889         return FALSE;
15890     }
15891
15892     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15893     /* return the extra info in global variiables             */
15894
15895     /* set defaults in case FEN is incomplete */
15896     board[EP_STATUS] = EP_UNKNOWN;
15897     for(i=0; i<nrCastlingRights; i++ ) {
15898         board[CASTLING][i] =
15899             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15900     }   /* assume possible unless obviously impossible */
15901     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15902     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15903     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15904                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15905     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15906     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15907     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15908                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15909     FENrulePlies = 0;
15910
15911     while(*p==' ') p++;
15912     if(nrCastlingRights) {
15913       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15914           /* castling indicator present, so default becomes no castlings */
15915           for(i=0; i<nrCastlingRights; i++ ) {
15916                  board[CASTLING][i] = NoRights;
15917           }
15918       }
15919       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15920              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15921              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15922              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15923         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15924
15925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15926             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15927             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15928         }
15929         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15930             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15931         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15932                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15933         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15934                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15935         switch(c) {
15936           case'K':
15937               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15938               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15939               board[CASTLING][2] = whiteKingFile;
15940               break;
15941           case'Q':
15942               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15943               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15944               board[CASTLING][2] = whiteKingFile;
15945               break;
15946           case'k':
15947               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15948               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15949               board[CASTLING][5] = blackKingFile;
15950               break;
15951           case'q':
15952               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15953               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15954               board[CASTLING][5] = blackKingFile;
15955           case '-':
15956               break;
15957           default: /* FRC castlings */
15958               if(c >= 'a') { /* black rights */
15959                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15960                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15961                   if(i == BOARD_RGHT) break;
15962                   board[CASTLING][5] = i;
15963                   c -= AAA;
15964                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15965                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15966                   if(c > i)
15967                       board[CASTLING][3] = c;
15968                   else
15969                       board[CASTLING][4] = c;
15970               } else { /* white rights */
15971                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15972                     if(board[0][i] == WhiteKing) break;
15973                   if(i == BOARD_RGHT) break;
15974                   board[CASTLING][2] = i;
15975                   c -= AAA - 'a' + 'A';
15976                   if(board[0][c] >= WhiteKing) break;
15977                   if(c > i)
15978                       board[CASTLING][0] = c;
15979                   else
15980                       board[CASTLING][1] = c;
15981               }
15982         }
15983       }
15984       for(i=0; i<nrCastlingRights; i++)
15985         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15986     if (appData.debugMode) {
15987         fprintf(debugFP, "FEN castling rights:");
15988         for(i=0; i<nrCastlingRights; i++)
15989         fprintf(debugFP, " %d", board[CASTLING][i]);
15990         fprintf(debugFP, "\n");
15991     }
15992
15993       while(*p==' ') p++;
15994     }
15995
15996     /* read e.p. field in games that know e.p. capture */
15997     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15998        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15999       if(*p=='-') {
16000         p++; board[EP_STATUS] = EP_NONE;
16001       } else {
16002          char c = *p++ - AAA;
16003
16004          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16005          if(*p >= '0' && *p <='9') p++;
16006          board[EP_STATUS] = c;
16007       }
16008     }
16009
16010
16011     if(sscanf(p, "%d", &i) == 1) {
16012         FENrulePlies = i; /* 50-move ply counter */
16013         /* (The move number is still ignored)    */
16014     }
16015
16016     return TRUE;
16017 }
16018
16019 void
16020 EditPositionPasteFEN(char *fen)
16021 {
16022   if (fen != NULL) {
16023     Board initial_position;
16024
16025     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16026       DisplayError(_("Bad FEN position in clipboard"), 0);
16027       return ;
16028     } else {
16029       int savedBlackPlaysFirst = blackPlaysFirst;
16030       EditPositionEvent();
16031       blackPlaysFirst = savedBlackPlaysFirst;
16032       CopyBoard(boards[0], initial_position);
16033       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16034       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16035       DisplayBothClocks();
16036       DrawPosition(FALSE, boards[currentMove]);
16037     }
16038   }
16039 }
16040
16041 static char cseq[12] = "\\   ";
16042
16043 Boolean set_cont_sequence(char *new_seq)
16044 {
16045     int len;
16046     Boolean ret;
16047
16048     // handle bad attempts to set the sequence
16049         if (!new_seq)
16050                 return 0; // acceptable error - no debug
16051
16052     len = strlen(new_seq);
16053     ret = (len > 0) && (len < sizeof(cseq));
16054     if (ret)
16055       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16056     else if (appData.debugMode)
16057       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16058     return ret;
16059 }
16060
16061 /*
16062     reformat a source message so words don't cross the width boundary.  internal
16063     newlines are not removed.  returns the wrapped size (no null character unless
16064     included in source message).  If dest is NULL, only calculate the size required
16065     for the dest buffer.  lp argument indicats line position upon entry, and it's
16066     passed back upon exit.
16067 */
16068 int wrap(char *dest, char *src, int count, int width, int *lp)
16069 {
16070     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16071
16072     cseq_len = strlen(cseq);
16073     old_line = line = *lp;
16074     ansi = len = clen = 0;
16075
16076     for (i=0; i < count; i++)
16077     {
16078         if (src[i] == '\033')
16079             ansi = 1;
16080
16081         // if we hit the width, back up
16082         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16083         {
16084             // store i & len in case the word is too long
16085             old_i = i, old_len = len;
16086
16087             // find the end of the last word
16088             while (i && src[i] != ' ' && src[i] != '\n')
16089             {
16090                 i--;
16091                 len--;
16092             }
16093
16094             // word too long?  restore i & len before splitting it
16095             if ((old_i-i+clen) >= width)
16096             {
16097                 i = old_i;
16098                 len = old_len;
16099             }
16100
16101             // extra space?
16102             if (i && src[i-1] == ' ')
16103                 len--;
16104
16105             if (src[i] != ' ' && src[i] != '\n')
16106             {
16107                 i--;
16108                 if (len)
16109                     len--;
16110             }
16111
16112             // now append the newline and continuation sequence
16113             if (dest)
16114                 dest[len] = '\n';
16115             len++;
16116             if (dest)
16117                 strncpy(dest+len, cseq, cseq_len);
16118             len += cseq_len;
16119             line = cseq_len;
16120             clen = cseq_len;
16121             continue;
16122         }
16123
16124         if (dest)
16125             dest[len] = src[i];
16126         len++;
16127         if (!ansi)
16128             line++;
16129         if (src[i] == '\n')
16130             line = 0;
16131         if (src[i] == 'm')
16132             ansi = 0;
16133     }
16134     if (dest && appData.debugMode)
16135     {
16136         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16137             count, width, line, len, *lp);
16138         show_bytes(debugFP, src, count);
16139         fprintf(debugFP, "\ndest: ");
16140         show_bytes(debugFP, dest, len);
16141         fprintf(debugFP, "\n");
16142     }
16143     *lp = dest ? line : old_line;
16144
16145     return len;
16146 }
16147
16148 // [HGM] vari: routines for shelving variations
16149
16150 void
16151 PushInner(int firstMove, int lastMove)
16152 {
16153         int i, j, nrMoves = lastMove - firstMove;
16154
16155         // push current tail of game on stack
16156         savedResult[storedGames] = gameInfo.result;
16157         savedDetails[storedGames] = gameInfo.resultDetails;
16158         gameInfo.resultDetails = NULL;
16159         savedFirst[storedGames] = firstMove;
16160         savedLast [storedGames] = lastMove;
16161         savedFramePtr[storedGames] = framePtr;
16162         framePtr -= nrMoves; // reserve space for the boards
16163         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16164             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16165             for(j=0; j<MOVE_LEN; j++)
16166                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16167             for(j=0; j<2*MOVE_LEN; j++)
16168                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16169             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16170             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16171             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16172             pvInfoList[firstMove+i-1].depth = 0;
16173             commentList[framePtr+i] = commentList[firstMove+i];
16174             commentList[firstMove+i] = NULL;
16175         }
16176
16177         storedGames++;
16178         forwardMostMove = firstMove; // truncate game so we can start variation
16179 }
16180
16181 void
16182 PushTail(int firstMove, int lastMove)
16183 {
16184         if(appData.icsActive) { // only in local mode
16185                 forwardMostMove = currentMove; // mimic old ICS behavior
16186                 return;
16187         }
16188         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16189
16190         PushInner(firstMove, lastMove);
16191         if(storedGames == 1) GreyRevert(FALSE);
16192 }
16193
16194 void
16195 PopInner(Boolean annotate)
16196 {
16197         int i, j, nrMoves;
16198         char buf[8000], moveBuf[20];
16199
16200         storedGames--;
16201         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16202         nrMoves = savedLast[storedGames] - currentMove;
16203         if(annotate) {
16204                 int cnt = 10;
16205                 if(!WhiteOnMove(currentMove))
16206                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16207                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16208                 for(i=currentMove; i<forwardMostMove; i++) {
16209                         if(WhiteOnMove(i))
16210                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16211                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16212                         strcat(buf, moveBuf);
16213                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16214                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16215                 }
16216                 strcat(buf, ")");
16217         }
16218         for(i=1; i<=nrMoves; i++) { // copy last variation back
16219             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16220             for(j=0; j<MOVE_LEN; j++)
16221                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16222             for(j=0; j<2*MOVE_LEN; j++)
16223                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16224             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16225             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16226             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16227             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16228             commentList[currentMove+i] = commentList[framePtr+i];
16229             commentList[framePtr+i] = NULL;
16230         }
16231         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16232         framePtr = savedFramePtr[storedGames];
16233         gameInfo.result = savedResult[storedGames];
16234         if(gameInfo.resultDetails != NULL) {
16235             free(gameInfo.resultDetails);
16236       }
16237         gameInfo.resultDetails = savedDetails[storedGames];
16238         forwardMostMove = currentMove + nrMoves;
16239 }
16240
16241 Boolean
16242 PopTail(Boolean annotate)
16243 {
16244         if(appData.icsActive) return FALSE; // only in local mode
16245         if(!storedGames) return FALSE; // sanity
16246         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16247
16248         PopInner(annotate);
16249
16250         if(storedGames == 0) GreyRevert(TRUE);
16251         return TRUE;
16252 }
16253
16254 void
16255 CleanupTail()
16256 {       // remove all shelved variations
16257         int i;
16258         for(i=0; i<storedGames; i++) {
16259             if(savedDetails[i])
16260                 free(savedDetails[i]);
16261             savedDetails[i] = NULL;
16262         }
16263         for(i=framePtr; i<MAX_MOVES; i++) {
16264                 if(commentList[i]) free(commentList[i]);
16265                 commentList[i] = NULL;
16266         }
16267         framePtr = MAX_MOVES-1;
16268         storedGames = 0;
16269 }
16270
16271 void
16272 LoadVariation(int index, char *text)
16273 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16274         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16275         int level = 0, move;
16276
16277         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16278         // first find outermost bracketing variation
16279         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16280             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16281                 if(*p == '{') wait = '}'; else
16282                 if(*p == '[') wait = ']'; else
16283                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16284                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16285             }
16286             if(*p == wait) wait = NULLCHAR; // closing ]} found
16287             p++;
16288         }
16289         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16290         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16291         end[1] = NULLCHAR; // clip off comment beyond variation
16292         ToNrEvent(currentMove-1);
16293         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16294         // kludge: use ParsePV() to append variation to game
16295         move = currentMove;
16296         ParsePV(start, TRUE, TRUE);
16297         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16298         ClearPremoveHighlights();
16299         CommentPopDown();
16300         ToNrEvent(currentMove+1);
16301 }
16302