fixed access rights to winboard language files (644 instead of 655)
[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     cps->supportsNPS = UNKNOWN;
800
801     /* [HGM] options */
802     cps->optionSettings  = appData.engOptions[n];
803
804     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805     cps->isUCI = appData.isUCI[n]; /* [AS] */
806     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807
808     if (appData.protocolVersion[n] > PROTOVER
809         || appData.protocolVersion[n] < 1)
810       {
811         char buf[MSG_SIZ];
812         int len;
813
814         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815                        appData.protocolVersion[n]);
816         if( (len > MSG_SIZ) && appData.debugMode )
817           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818
819         DisplayFatalError(buf, 0, 2);
820       }
821     else
822       {
823         cps->protocolVersion = appData.protocolVersion[n];
824       }
825
826     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
827 }
828
829 ChessProgramState *savCps;
830
831 void
832 LoadEngine()
833 {
834     int i;
835     if(WaitForEngine(savCps, LoadEngine)) return;
836     CommonEngineInit(); // recalculate time odds
837     if(gameInfo.variant != StringToVariant(appData.variant)) {
838         // we changed variant when loading the engine; this forces us to reset
839         Reset(TRUE, savCps != &first);
840         EditGameEvent(); // for consistency with other path, as Reset changes mode
841     }
842     InitChessProgram(savCps, FALSE);
843     SendToProgram("force\n", savCps);
844     DisplayMessage("", "");
845     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
847     ThawUI();
848     SetGNUMode();
849 }
850
851 void
852 ReplaceEngine(ChessProgramState *cps, int n)
853 {
854     EditGameEvent();
855     UnloadEngine(cps);
856     appData.noChessProgram = FALSE;
857     appData.clockMode = TRUE;
858     InitEngine(cps, n);
859     if(n) return; // only startup first engine immediately; second can wait
860     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861     LoadEngine();
862 }
863
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
866
867 static char resetOptions[] = 
868         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
869         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
870
871 void
872 Load(ChessProgramState *cps, int i)
873 {
874     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
875     if(engineLine[0]) { // an engine was selected from the combo box
876         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
877         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
878         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
879         ParseArgsFromString(buf);
880         SwapEngines(i);
881         ReplaceEngine(cps, i);
882         return;
883     }
884     p = engineName;
885     while(q = strchr(p, SLASH)) p = q+1;
886     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
887     if(engineDir[0] != NULLCHAR)
888         appData.directory[i] = engineDir;
889     else if(p != engineName) { // derive directory from engine path, when not given
890         p[-1] = 0;
891         appData.directory[i] = strdup(engineName);
892         p[-1] = SLASH;
893     } else appData.directory[i] = ".";
894     if(params[0]) {
895         snprintf(command, MSG_SIZ, "%s %s", p, params);
896         p = command;
897     }
898     appData.chessProgram[i] = strdup(p);
899     appData.isUCI[i] = isUCI;
900     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
901     appData.hasOwnBookUCI[i] = hasBook;
902     if(!nickName[0]) useNick = FALSE;
903     if(useNick) ASSIGN(appData.pgnName[i], nickName);
904     if(addToList) {
905         int len;
906         q = firstChessProgramNames;
907         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
908         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
909                         useNick ? " -fn \"" : "",
910                         useNick ? nickName : "",
911                         useNick ? "\"" : "",
912                         v1 ? " -firstProtocolVersion 1" : "",
913                         hasBook ? "" : " -fNoOwnBookUCI",
914                         isUCI ? " -fUCI" : "",
915                         storeVariant ? " -variant " : "",
916                         storeVariant ? VariantName(gameInfo.variant) : "");
917         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
918         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
919         if(q)   free(q);
920     }
921     ReplaceEngine(cps, i);
922 }
923
924 void
925 InitTimeControls()
926 {
927     int matched, min, sec;
928     /*
929      * Parse timeControl resource
930      */
931     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
932                           appData.movesPerSession)) {
933         char buf[MSG_SIZ];
934         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
935         DisplayFatalError(buf, 0, 2);
936     }
937
938     /*
939      * Parse searchTime resource
940      */
941     if (*appData.searchTime != NULLCHAR) {
942         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
943         if (matched == 1) {
944             searchTime = min * 60;
945         } else if (matched == 2) {
946             searchTime = min * 60 + sec;
947         } else {
948             char buf[MSG_SIZ];
949             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
950             DisplayFatalError(buf, 0, 2);
951         }
952     }
953 }
954
955 void
956 InitBackEnd1()
957 {
958
959     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
960     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
961
962     GetTimeMark(&programStartTime);
963     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
964     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
965
966     ClearProgramStats();
967     programStats.ok_to_send = 1;
968     programStats.seen_stat = 0;
969
970     /*
971      * Initialize game list
972      */
973     ListNew(&gameList);
974
975
976     /*
977      * Internet chess server status
978      */
979     if (appData.icsActive) {
980         appData.matchMode = FALSE;
981         appData.matchGames = 0;
982 #if ZIPPY
983         appData.noChessProgram = !appData.zippyPlay;
984 #else
985         appData.zippyPlay = FALSE;
986         appData.zippyTalk = FALSE;
987         appData.noChessProgram = TRUE;
988 #endif
989         if (*appData.icsHelper != NULLCHAR) {
990             appData.useTelnet = TRUE;
991             appData.telnetProgram = appData.icsHelper;
992         }
993     } else {
994         appData.zippyTalk = appData.zippyPlay = FALSE;
995     }
996
997     /* [AS] Initialize pv info list [HGM] and game state */
998     {
999         int i, j;
1000
1001         for( i=0; i<=framePtr; i++ ) {
1002             pvInfoList[i].depth = -1;
1003             boards[i][EP_STATUS] = EP_NONE;
1004             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1005         }
1006     }
1007
1008     InitTimeControls();
1009
1010     /* [AS] Adjudication threshold */
1011     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1012
1013     InitEngine(&first, 0);
1014     InitEngine(&second, 1);
1015     CommonEngineInit();
1016
1017     if (appData.icsActive) {
1018         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1019     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1020         appData.clockMode = FALSE;
1021         first.sendTime = second.sendTime = 0;
1022     }
1023
1024 #if ZIPPY
1025     /* Override some settings from environment variables, for backward
1026        compatibility.  Unfortunately it's not feasible to have the env
1027        vars just set defaults, at least in xboard.  Ugh.
1028     */
1029     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030       ZippyInit();
1031     }
1032 #endif
1033
1034     if (!appData.icsActive) {
1035       char buf[MSG_SIZ];
1036       int len;
1037
1038       /* Check for variants that are supported only in ICS mode,
1039          or not at all.  Some that are accepted here nevertheless
1040          have bugs; see comments below.
1041       */
1042       VariantClass variant = StringToVariant(appData.variant);
1043       switch (variant) {
1044       case VariantBughouse:     /* need four players and two boards */
1045       case VariantKriegspiel:   /* need to hide pieces and move details */
1046         /* case VariantFischeRandom: (Fabien: moved below) */
1047         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1048         if( (len > MSG_SIZ) && appData.debugMode )
1049           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1050
1051         DisplayFatalError(buf, 0, 2);
1052         return;
1053
1054       case VariantUnknown:
1055       case VariantLoadable:
1056       case Variant29:
1057       case Variant30:
1058       case Variant31:
1059       case Variant32:
1060       case Variant33:
1061       case Variant34:
1062       case Variant35:
1063       case Variant36:
1064       default:
1065         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1073       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1074       case VariantGothic:     /* [HGM] should work */
1075       case VariantCapablanca: /* [HGM] should work */
1076       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1077       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1078       case VariantKnightmate: /* [HGM] should work */
1079       case VariantCylinder:   /* [HGM] untested */
1080       case VariantFalcon:     /* [HGM] untested */
1081       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1082                                  offboard interposition not understood */
1083       case VariantNormal:     /* definitely works! */
1084       case VariantWildCastle: /* pieces not automatically shuffled */
1085       case VariantNoCastle:   /* pieces not automatically shuffled */
1086       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1087       case VariantLosers:     /* should work except for win condition,
1088                                  and doesn't know captures are mandatory */
1089       case VariantSuicide:    /* should work except for win condition,
1090                                  and doesn't know captures are mandatory */
1091       case VariantGiveaway:   /* should work except for win condition,
1092                                  and doesn't know captures are mandatory */
1093       case VariantTwoKings:   /* should work */
1094       case VariantAtomic:     /* should work except for win condition */
1095       case Variant3Check:     /* should work except for win condition */
1096       case VariantShatranj:   /* should work except for all win conditions */
1097       case VariantMakruk:     /* should work except for daw countdown */
1098       case VariantBerolina:   /* might work if TestLegality is off */
1099       case VariantCapaRandom: /* should work */
1100       case VariantJanus:      /* should work */
1101       case VariantSuper:      /* experimental */
1102       case VariantGreat:      /* experimental, requires legality testing to be off */
1103       case VariantSChess:     /* S-Chess, should work */
1104       case VariantSpartan:    /* should work */
1105         break;
1106       }
1107     }
1108
1109 }
1110
1111 int NextIntegerFromString( char ** str, long * value )
1112 {
1113     int result = -1;
1114     char * s = *str;
1115
1116     while( *s == ' ' || *s == '\t' ) {
1117         s++;
1118     }
1119
1120     *value = 0;
1121
1122     if( *s >= '0' && *s <= '9' ) {
1123         while( *s >= '0' && *s <= '9' ) {
1124             *value = *value * 10 + (*s - '0');
1125             s++;
1126         }
1127
1128         result = 0;
1129     }
1130
1131     *str = s;
1132
1133     return result;
1134 }
1135
1136 int NextTimeControlFromString( char ** str, long * value )
1137 {
1138     long temp;
1139     int result = NextIntegerFromString( str, &temp );
1140
1141     if( result == 0 ) {
1142         *value = temp * 60; /* Minutes */
1143         if( **str == ':' ) {
1144             (*str)++;
1145             result = NextIntegerFromString( str, &temp );
1146             *value += temp; /* Seconds */
1147         }
1148     }
1149
1150     return result;
1151 }
1152
1153 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1154 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1155     int result = -1, type = 0; long temp, temp2;
1156
1157     if(**str != ':') return -1; // old params remain in force!
1158     (*str)++;
1159     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1160     if( NextIntegerFromString( str, &temp ) ) return -1;
1161     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1162
1163     if(**str != '/') {
1164         /* time only: incremental or sudden-death time control */
1165         if(**str == '+') { /* increment follows; read it */
1166             (*str)++;
1167             if(**str == '!') type = *(*str)++; // Bronstein TC
1168             if(result = NextIntegerFromString( str, &temp2)) return -1;
1169             *inc = temp2 * 1000;
1170             if(**str == '.') { // read fraction of increment
1171                 char *start = ++(*str);
1172                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1173                 temp2 *= 1000;
1174                 while(start++ < *str) temp2 /= 10;
1175                 *inc += temp2;
1176             }
1177         } else *inc = 0;
1178         *moves = 0; *tc = temp * 1000; *incType = type;
1179         return 0;
1180     }
1181
1182     (*str)++; /* classical time control */
1183     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1184
1185     if(result == 0) {
1186         *moves = temp;
1187         *tc    = temp2 * 1000;
1188         *inc   = 0;
1189         *incType = type;
1190     }
1191     return result;
1192 }
1193
1194 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1195 {   /* [HGM] get time to add from the multi-session time-control string */
1196     int incType, moves=1; /* kludge to force reading of first session */
1197     long time, increment;
1198     char *s = tcString;
1199
1200     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1201     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1202     do {
1203         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1204         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1205         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1206         if(movenr == -1) return time;    /* last move before new session     */
1207         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1208         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1209         if(!moves) return increment;     /* current session is incremental   */
1210         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1211     } while(movenr >= -1);               /* try again for next session       */
1212
1213     return 0; // no new time quota on this move
1214 }
1215
1216 int
1217 ParseTimeControl(tc, ti, mps)
1218      char *tc;
1219      float ti;
1220      int mps;
1221 {
1222   long tc1;
1223   long tc2;
1224   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1225   int min, sec=0;
1226
1227   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1228   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1229       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1230   if(ti > 0) {
1231
1232     if(mps)
1233       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1234     else 
1235       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1236   } else {
1237     if(mps)
1238       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1239     else 
1240       snprintf(buf, MSG_SIZ, ":%s", mytc);
1241   }
1242   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1243   
1244   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245     return FALSE;
1246   }
1247
1248   if( *tc == '/' ) {
1249     /* Parse second time control */
1250     tc++;
1251
1252     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1253       return FALSE;
1254     }
1255
1256     if( tc2 == 0 ) {
1257       return FALSE;
1258     }
1259
1260     timeControl_2 = tc2 * 1000;
1261   }
1262   else {
1263     timeControl_2 = 0;
1264   }
1265
1266   if( tc1 == 0 ) {
1267     return FALSE;
1268   }
1269
1270   timeControl = tc1 * 1000;
1271
1272   if (ti >= 0) {
1273     timeIncrement = ti * 1000;  /* convert to ms */
1274     movesPerSession = 0;
1275   } else {
1276     timeIncrement = 0;
1277     movesPerSession = mps;
1278   }
1279   return TRUE;
1280 }
1281
1282 void
1283 InitBackEnd2()
1284 {
1285     if (appData.debugMode) {
1286         fprintf(debugFP, "%s\n", programVersion);
1287     }
1288
1289     set_cont_sequence(appData.wrapContSeq);
1290     if (appData.matchGames > 0) {
1291         appData.matchMode = TRUE;
1292     } else if (appData.matchMode) {
1293         appData.matchGames = 1;
1294     }
1295     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1296         appData.matchGames = appData.sameColorGames;
1297     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1298         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1299         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1300     }
1301     Reset(TRUE, FALSE);
1302     if (appData.noChessProgram || first.protocolVersion == 1) {
1303       InitBackEnd3();
1304     } else {
1305       /* kludge: allow timeout for initial "feature" commands */
1306       FreezeUI();
1307       DisplayMessage("", _("Starting chess program"));
1308       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309     }
1310 }
1311
1312 int
1313 CalculateIndex(int index, int gameNr)
1314 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1315     int res;
1316     if(index > 0) return index; // fixed nmber
1317     if(index == 0) return 1;
1318     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1319     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320     return res;
1321 }
1322
1323 int
1324 LoadGameOrPosition(int gameNr)
1325 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1326     if (*appData.loadGameFile != NULLCHAR) {
1327         if (!LoadGameFromFile(appData.loadGameFile,
1328                 CalculateIndex(appData.loadGameIndex, gameNr),
1329                               appData.loadGameFile, FALSE)) {
1330             DisplayFatalError(_("Bad game file"), 0, 1);
1331             return 0;
1332         }
1333     } else if (*appData.loadPositionFile != NULLCHAR) {
1334         if (!LoadPositionFromFile(appData.loadPositionFile,
1335                 CalculateIndex(appData.loadPositionIndex, gameNr),
1336                                   appData.loadPositionFile)) {
1337             DisplayFatalError(_("Bad position file"), 0, 1);
1338             return 0;
1339         }
1340     }
1341     return 1;
1342 }
1343
1344 void
1345 ReserveGame(int gameNr, char resChar)
1346 {
1347     FILE *tf = fopen(appData.tourneyFile, "r+");
1348     char *p, *q, c, buf[MSG_SIZ];
1349     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1350     safeStrCpy(buf, lastMsg, MSG_SIZ);
1351     DisplayMessage(_("Pick new game"), "");
1352     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1353     ParseArgsFromFile(tf);
1354     p = q = appData.results;
1355     if(appData.debugMode) {
1356       char *r = appData.participants;
1357       fprintf(debugFP, "results = '%s'\n", p);
1358       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1359       fprintf(debugFP, "\n");
1360     }
1361     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1362     nextGame = q - p;
1363     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1364     safeStrCpy(q, p, strlen(p) + 2);
1365     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1366     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1367     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1368         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1369         q[nextGame] = '*';
1370     }
1371     fseek(tf, -(strlen(p)+4), SEEK_END);
1372     c = fgetc(tf);
1373     if(c != '"') // depending on DOS or Unix line endings we can be one off
1374          fseek(tf, -(strlen(p)+2), SEEK_END);
1375     else fseek(tf, -(strlen(p)+3), SEEK_END);
1376     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1377     DisplayMessage(buf, "");
1378     free(p); appData.results = q;
1379     if(nextGame <= appData.matchGames && resChar != ' ' &&
1380        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1381         UnloadEngine(&first);  // next game belongs to other pairing;
1382         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383     }
1384 }
1385
1386 void
1387 MatchEvent(int mode)
1388 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1389         int dummy;
1390         if(matchMode) { // already in match mode: switch it off
1391             abortMatch = TRUE;
1392             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1393             ModeHighlight(); // kludgey way to remove checkmark...
1394             return;
1395         }
1396 //      if(gameMode != BeginningOfGame) {
1397 //          DisplayError(_("You can only start a match from the initial position."), 0);
1398 //          return;
1399 //      }
1400         abortMatch = FALSE;
1401         appData.matchGames = appData.defaultMatchGames;
1402         /* Set up machine vs. machine match */
1403         nextGame = 0;
1404         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1405         if(appData.tourneyFile[0]) {
1406             ReserveGame(-1, 0);
1407             if(nextGame > appData.matchGames) {
1408                 char buf[MSG_SIZ];
1409                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1410                 DisplayError(buf, 0);
1411                 appData.tourneyFile[0] = 0;
1412                 return;
1413             }
1414         } else
1415         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1416             DisplayFatalError(_("Can't have a match with no chess programs"),
1417                               0, 2);
1418             return;
1419         }
1420         matchMode = mode;
1421         matchGame = roundNr = 1;
1422         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1423         NextMatchGame();
1424 }
1425
1426 void
1427 InitBackEnd3 P((void))
1428 {
1429     GameMode initialMode;
1430     char buf[MSG_SIZ];
1431     int err, len;
1432
1433     InitChessProgram(&first, startedFromSetupPosition);
1434
1435     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1436         free(programVersion);
1437         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1438         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1439     }
1440
1441     if (appData.icsActive) {
1442 #ifdef WIN32
1443         /* [DM] Make a console window if needed [HGM] merged ifs */
1444         ConsoleCreate();
1445 #endif
1446         err = establish();
1447         if (err != 0)
1448           {
1449             if (*appData.icsCommPort != NULLCHAR)
1450               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1451                              appData.icsCommPort);
1452             else
1453               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1454                         appData.icsHost, appData.icsPort);
1455
1456             if( (len > MSG_SIZ) && appData.debugMode )
1457               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1458
1459             DisplayFatalError(buf, err, 1);
1460             return;
1461         }
1462         SetICSMode();
1463         telnetISR =
1464           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1465         fromUserISR =
1466           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1467         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1468             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469     } else if (appData.noChessProgram) {
1470         SetNCPMode();
1471     } else {
1472         SetGNUMode();
1473     }
1474
1475     if (*appData.cmailGameName != NULLCHAR) {
1476         SetCmailMode();
1477         OpenLoopback(&cmailPR);
1478         cmailISR =
1479           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1480     }
1481
1482     ThawUI();
1483     DisplayMessage("", "");
1484     if (StrCaseCmp(appData.initialMode, "") == 0) {
1485       initialMode = BeginningOfGame;
1486       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1487         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1488         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1489         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1490         ModeHighlight();
1491       }
1492     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1493       initialMode = TwoMachinesPlay;
1494     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1495       initialMode = AnalyzeFile;
1496     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1497       initialMode = AnalyzeMode;
1498     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1499       initialMode = MachinePlaysWhite;
1500     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1501       initialMode = MachinePlaysBlack;
1502     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1503       initialMode = EditGame;
1504     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1505       initialMode = EditPosition;
1506     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1507       initialMode = Training;
1508     } else {
1509       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1510       if( (len > MSG_SIZ) && appData.debugMode )
1511         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1512
1513       DisplayFatalError(buf, 0, 2);
1514       return;
1515     }
1516
1517     if (appData.matchMode) {
1518         if(appData.tourneyFile[0]) { // start tourney from command line
1519             FILE *f;
1520             if(f = fopen(appData.tourneyFile, "r")) {
1521                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1522                 fclose(f);
1523             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1524         }
1525         MatchEvent(TRUE);
1526     } else if (*appData.cmailGameName != NULLCHAR) {
1527         /* Set up cmail mode */
1528         ReloadCmailMsgEvent(TRUE);
1529     } else {
1530         /* Set up other modes */
1531         if (initialMode == AnalyzeFile) {
1532           if (*appData.loadGameFile == NULLCHAR) {
1533             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1534             return;
1535           }
1536         }
1537         if (*appData.loadGameFile != NULLCHAR) {
1538             (void) LoadGameFromFile(appData.loadGameFile,
1539                                     appData.loadGameIndex,
1540                                     appData.loadGameFile, TRUE);
1541         } else if (*appData.loadPositionFile != NULLCHAR) {
1542             (void) LoadPositionFromFile(appData.loadPositionFile,
1543                                         appData.loadPositionIndex,
1544                                         appData.loadPositionFile);
1545             /* [HGM] try to make self-starting even after FEN load */
1546             /* to allow automatic setup of fairy variants with wtm */
1547             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1548                 gameMode = BeginningOfGame;
1549                 setboardSpoiledMachineBlack = 1;
1550             }
1551             /* [HGM] loadPos: make that every new game uses the setup */
1552             /* from file as long as we do not switch variant          */
1553             if(!blackPlaysFirst) {
1554                 startedFromPositionFile = TRUE;
1555                 CopyBoard(filePosition, boards[0]);
1556             }
1557         }
1558         if (initialMode == AnalyzeMode) {
1559           if (appData.noChessProgram) {
1560             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1561             return;
1562           }
1563           if (appData.icsActive) {
1564             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1565             return;
1566           }
1567           AnalyzeModeEvent();
1568         } else if (initialMode == AnalyzeFile) {
1569           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1570           ShowThinkingEvent();
1571           AnalyzeFileEvent();
1572           AnalysisPeriodicEvent(1);
1573         } else if (initialMode == MachinePlaysWhite) {
1574           if (appData.noChessProgram) {
1575             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1576                               0, 2);
1577             return;
1578           }
1579           if (appData.icsActive) {
1580             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1581                               0, 2);
1582             return;
1583           }
1584           MachineWhiteEvent();
1585         } else if (initialMode == MachinePlaysBlack) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1588                               0, 2);
1589             return;
1590           }
1591           if (appData.icsActive) {
1592             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1593                               0, 2);
1594             return;
1595           }
1596           MachineBlackEvent();
1597         } else if (initialMode == TwoMachinesPlay) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1600                               0, 2);
1601             return;
1602           }
1603           if (appData.icsActive) {
1604             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605                               0, 2);
1606             return;
1607           }
1608           TwoMachinesEvent();
1609         } else if (initialMode == EditGame) {
1610           EditGameEvent();
1611         } else if (initialMode == EditPosition) {
1612           EditPositionEvent();
1613         } else if (initialMode == Training) {
1614           if (*appData.loadGameFile == NULLCHAR) {
1615             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1616             return;
1617           }
1618           TrainingEvent();
1619         }
1620     }
1621 }
1622
1623 /*
1624  * Establish will establish a contact to a remote host.port.
1625  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1626  *  used to talk to the host.
1627  * Returns 0 if okay, error code if not.
1628  */
1629 int
1630 establish()
1631 {
1632     char buf[MSG_SIZ];
1633
1634     if (*appData.icsCommPort != NULLCHAR) {
1635         /* Talk to the host through a serial comm port */
1636         return OpenCommPort(appData.icsCommPort, &icsPR);
1637
1638     } else if (*appData.gateway != NULLCHAR) {
1639         if (*appData.remoteShell == NULLCHAR) {
1640             /* Use the rcmd protocol to run telnet program on a gateway host */
1641             snprintf(buf, sizeof(buf), "%s %s %s",
1642                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1643             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1644
1645         } else {
1646             /* Use the rsh program to run telnet program on a gateway host */
1647             if (*appData.remoteUser == NULLCHAR) {
1648                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1649                         appData.gateway, appData.telnetProgram,
1650                         appData.icsHost, appData.icsPort);
1651             } else {
1652                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1653                         appData.remoteShell, appData.gateway,
1654                         appData.remoteUser, appData.telnetProgram,
1655                         appData.icsHost, appData.icsPort);
1656             }
1657             return StartChildProcess(buf, "", &icsPR);
1658
1659         }
1660     } else if (appData.useTelnet) {
1661         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1662
1663     } else {
1664         /* TCP socket interface differs somewhat between
1665            Unix and NT; handle details in the front end.
1666            */
1667         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1668     }
1669 }
1670
1671 void EscapeExpand(char *p, char *q)
1672 {       // [HGM] initstring: routine to shape up string arguments
1673         while(*p++ = *q++) if(p[-1] == '\\')
1674             switch(*q++) {
1675                 case 'n': p[-1] = '\n'; break;
1676                 case 'r': p[-1] = '\r'; break;
1677                 case 't': p[-1] = '\t'; break;
1678                 case '\\': p[-1] = '\\'; break;
1679                 case 0: *p = 0; return;
1680                 default: p[-1] = q[-1]; break;
1681             }
1682 }
1683
1684 void
1685 show_bytes(fp, buf, count)
1686      FILE *fp;
1687      char *buf;
1688      int count;
1689 {
1690     while (count--) {
1691         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1692             fprintf(fp, "\\%03o", *buf & 0xff);
1693         } else {
1694             putc(*buf, fp);
1695         }
1696         buf++;
1697     }
1698     fflush(fp);
1699 }
1700
1701 /* Returns an errno value */
1702 int
1703 OutputMaybeTelnet(pr, message, count, outError)
1704      ProcRef pr;
1705      char *message;
1706      int count;
1707      int *outError;
1708 {
1709     char buf[8192], *p, *q, *buflim;
1710     int left, newcount, outcount;
1711
1712     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1713         *appData.gateway != NULLCHAR) {
1714         if (appData.debugMode) {
1715             fprintf(debugFP, ">ICS: ");
1716             show_bytes(debugFP, message, count);
1717             fprintf(debugFP, "\n");
1718         }
1719         return OutputToProcess(pr, message, count, outError);
1720     }
1721
1722     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1723     p = message;
1724     q = buf;
1725     left = count;
1726     newcount = 0;
1727     while (left) {
1728         if (q >= buflim) {
1729             if (appData.debugMode) {
1730                 fprintf(debugFP, ">ICS: ");
1731                 show_bytes(debugFP, buf, newcount);
1732                 fprintf(debugFP, "\n");
1733             }
1734             outcount = OutputToProcess(pr, buf, newcount, outError);
1735             if (outcount < newcount) return -1; /* to be sure */
1736             q = buf;
1737             newcount = 0;
1738         }
1739         if (*p == '\n') {
1740             *q++ = '\r';
1741             newcount++;
1742         } else if (((unsigned char) *p) == TN_IAC) {
1743             *q++ = (char) TN_IAC;
1744             newcount ++;
1745         }
1746         *q++ = *p++;
1747         newcount++;
1748         left--;
1749     }
1750     if (appData.debugMode) {
1751         fprintf(debugFP, ">ICS: ");
1752         show_bytes(debugFP, buf, newcount);
1753         fprintf(debugFP, "\n");
1754     }
1755     outcount = OutputToProcess(pr, buf, newcount, outError);
1756     if (outcount < newcount) return -1; /* to be sure */
1757     return count;
1758 }
1759
1760 void
1761 read_from_player(isr, closure, message, count, error)
1762      InputSourceRef isr;
1763      VOIDSTAR closure;
1764      char *message;
1765      int count;
1766      int error;
1767 {
1768     int outError, outCount;
1769     static int gotEof = 0;
1770
1771     /* Pass data read from player on to ICS */
1772     if (count > 0) {
1773         gotEof = 0;
1774         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1775         if (outCount < count) {
1776             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1777         }
1778     } else if (count < 0) {
1779         RemoveInputSource(isr);
1780         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1781     } else if (gotEof++ > 0) {
1782         RemoveInputSource(isr);
1783         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1784     }
1785 }
1786
1787 void
1788 KeepAlive()
1789 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1790     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1791     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1792     SendToICS("date\n");
1793     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1794 }
1795
1796 /* added routine for printf style output to ics */
1797 void ics_printf(char *format, ...)
1798 {
1799     char buffer[MSG_SIZ];
1800     va_list args;
1801
1802     va_start(args, format);
1803     vsnprintf(buffer, sizeof(buffer), format, args);
1804     buffer[sizeof(buffer)-1] = '\0';
1805     SendToICS(buffer);
1806     va_end(args);
1807 }
1808
1809 void
1810 SendToICS(s)
1811      char *s;
1812 {
1813     int count, outCount, outError;
1814
1815     if (icsPR == NULL) return;
1816
1817     count = strlen(s);
1818     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1819     if (outCount < count) {
1820         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821     }
1822 }
1823
1824 /* This is used for sending logon scripts to the ICS. Sending
1825    without a delay causes problems when using timestamp on ICC
1826    (at least on my machine). */
1827 void
1828 SendToICSDelayed(s,msdelay)
1829      char *s;
1830      long msdelay;
1831 {
1832     int count, outCount, outError;
1833
1834     if (icsPR == NULL) return;
1835
1836     count = strlen(s);
1837     if (appData.debugMode) {
1838         fprintf(debugFP, ">ICS: ");
1839         show_bytes(debugFP, s, count);
1840         fprintf(debugFP, "\n");
1841     }
1842     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1843                                       msdelay);
1844     if (outCount < count) {
1845         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846     }
1847 }
1848
1849
1850 /* Remove all highlighting escape sequences in s
1851    Also deletes any suffix starting with '('
1852    */
1853 char *
1854 StripHighlightAndTitle(s)
1855      char *s;
1856 {
1857     static char retbuf[MSG_SIZ];
1858     char *p = retbuf;
1859
1860     while (*s != NULLCHAR) {
1861         while (*s == '\033') {
1862             while (*s != NULLCHAR && !isalpha(*s)) s++;
1863             if (*s != NULLCHAR) s++;
1864         }
1865         while (*s != NULLCHAR && *s != '\033') {
1866             if (*s == '(' || *s == '[') {
1867                 *p = NULLCHAR;
1868                 return retbuf;
1869             }
1870             *p++ = *s++;
1871         }
1872     }
1873     *p = NULLCHAR;
1874     return retbuf;
1875 }
1876
1877 /* Remove all highlighting escape sequences in s */
1878 char *
1879 StripHighlight(s)
1880      char *s;
1881 {
1882     static char retbuf[MSG_SIZ];
1883     char *p = retbuf;
1884
1885     while (*s != NULLCHAR) {
1886         while (*s == '\033') {
1887             while (*s != NULLCHAR && !isalpha(*s)) s++;
1888             if (*s != NULLCHAR) s++;
1889         }
1890         while (*s != NULLCHAR && *s != '\033') {
1891             *p++ = *s++;
1892         }
1893     }
1894     *p = NULLCHAR;
1895     return retbuf;
1896 }
1897
1898 char *variantNames[] = VARIANT_NAMES;
1899 char *
1900 VariantName(v)
1901      VariantClass v;
1902 {
1903     return variantNames[v];
1904 }
1905
1906
1907 /* Identify a variant from the strings the chess servers use or the
1908    PGN Variant tag names we use. */
1909 VariantClass
1910 StringToVariant(e)
1911      char *e;
1912 {
1913     char *p;
1914     int wnum = -1;
1915     VariantClass v = VariantNormal;
1916     int i, found = FALSE;
1917     char buf[MSG_SIZ];
1918     int len;
1919
1920     if (!e) return v;
1921
1922     /* [HGM] skip over optional board-size prefixes */
1923     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1924         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1925         while( *e++ != '_');
1926     }
1927
1928     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1929         v = VariantNormal;
1930         found = TRUE;
1931     } else
1932     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1933       if (StrCaseStr(e, variantNames[i])) {
1934         v = (VariantClass) i;
1935         found = TRUE;
1936         break;
1937       }
1938     }
1939
1940     if (!found) {
1941       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1942           || StrCaseStr(e, "wild/fr")
1943           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1944         v = VariantFischeRandom;
1945       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1946                  (i = 1, p = StrCaseStr(e, "w"))) {
1947         p += i;
1948         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1949         if (isdigit(*p)) {
1950           wnum = atoi(p);
1951         } else {
1952           wnum = -1;
1953         }
1954         switch (wnum) {
1955         case 0: /* FICS only, actually */
1956         case 1:
1957           /* Castling legal even if K starts on d-file */
1958           v = VariantWildCastle;
1959           break;
1960         case 2:
1961         case 3:
1962         case 4:
1963           /* Castling illegal even if K & R happen to start in
1964              normal positions. */
1965           v = VariantNoCastle;
1966           break;
1967         case 5:
1968         case 7:
1969         case 8:
1970         case 10:
1971         case 11:
1972         case 12:
1973         case 13:
1974         case 14:
1975         case 15:
1976         case 18:
1977         case 19:
1978           /* Castling legal iff K & R start in normal positions */
1979           v = VariantNormal;
1980           break;
1981         case 6:
1982         case 20:
1983         case 21:
1984           /* Special wilds for position setup; unclear what to do here */
1985           v = VariantLoadable;
1986           break;
1987         case 9:
1988           /* Bizarre ICC game */
1989           v = VariantTwoKings;
1990           break;
1991         case 16:
1992           v = VariantKriegspiel;
1993           break;
1994         case 17:
1995           v = VariantLosers;
1996           break;
1997         case 22:
1998           v = VariantFischeRandom;
1999           break;
2000         case 23:
2001           v = VariantCrazyhouse;
2002           break;
2003         case 24:
2004           v = VariantBughouse;
2005           break;
2006         case 25:
2007           v = Variant3Check;
2008           break;
2009         case 26:
2010           /* Not quite the same as FICS suicide! */
2011           v = VariantGiveaway;
2012           break;
2013         case 27:
2014           v = VariantAtomic;
2015           break;
2016         case 28:
2017           v = VariantShatranj;
2018           break;
2019
2020         /* Temporary names for future ICC types.  The name *will* change in
2021            the next xboard/WinBoard release after ICC defines it. */
2022         case 29:
2023           v = Variant29;
2024           break;
2025         case 30:
2026           v = Variant30;
2027           break;
2028         case 31:
2029           v = Variant31;
2030           break;
2031         case 32:
2032           v = Variant32;
2033           break;
2034         case 33:
2035           v = Variant33;
2036           break;
2037         case 34:
2038           v = Variant34;
2039           break;
2040         case 35:
2041           v = Variant35;
2042           break;
2043         case 36:
2044           v = Variant36;
2045           break;
2046         case 37:
2047           v = VariantShogi;
2048           break;
2049         case 38:
2050           v = VariantXiangqi;
2051           break;
2052         case 39:
2053           v = VariantCourier;
2054           break;
2055         case 40:
2056           v = VariantGothic;
2057           break;
2058         case 41:
2059           v = VariantCapablanca;
2060           break;
2061         case 42:
2062           v = VariantKnightmate;
2063           break;
2064         case 43:
2065           v = VariantFairy;
2066           break;
2067         case 44:
2068           v = VariantCylinder;
2069           break;
2070         case 45:
2071           v = VariantFalcon;
2072           break;
2073         case 46:
2074           v = VariantCapaRandom;
2075           break;
2076         case 47:
2077           v = VariantBerolina;
2078           break;
2079         case 48:
2080           v = VariantJanus;
2081           break;
2082         case 49:
2083           v = VariantSuper;
2084           break;
2085         case 50:
2086           v = VariantGreat;
2087           break;
2088         case -1:
2089           /* Found "wild" or "w" in the string but no number;
2090              must assume it's normal chess. */
2091           v = VariantNormal;
2092           break;
2093         default:
2094           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2095           if( (len > MSG_SIZ) && appData.debugMode )
2096             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2097
2098           DisplayError(buf, 0);
2099           v = VariantUnknown;
2100           break;
2101         }
2102       }
2103     }
2104     if (appData.debugMode) {
2105       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2106               e, wnum, VariantName(v));
2107     }
2108     return v;
2109 }
2110
2111 static int leftover_start = 0, leftover_len = 0;
2112 char star_match[STAR_MATCH_N][MSG_SIZ];
2113
2114 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2115    advance *index beyond it, and set leftover_start to the new value of
2116    *index; else return FALSE.  If pattern contains the character '*', it
2117    matches any sequence of characters not containing '\r', '\n', or the
2118    character following the '*' (if any), and the matched sequence(s) are
2119    copied into star_match.
2120    */
2121 int
2122 looking_at(buf, index, pattern)
2123      char *buf;
2124      int *index;
2125      char *pattern;
2126 {
2127     char *bufp = &buf[*index], *patternp = pattern;
2128     int star_count = 0;
2129     char *matchp = star_match[0];
2130
2131     for (;;) {
2132         if (*patternp == NULLCHAR) {
2133             *index = leftover_start = bufp - buf;
2134             *matchp = NULLCHAR;
2135             return TRUE;
2136         }
2137         if (*bufp == NULLCHAR) return FALSE;
2138         if (*patternp == '*') {
2139             if (*bufp == *(patternp + 1)) {
2140                 *matchp = NULLCHAR;
2141                 matchp = star_match[++star_count];
2142                 patternp += 2;
2143                 bufp++;
2144                 continue;
2145             } else if (*bufp == '\n' || *bufp == '\r') {
2146                 patternp++;
2147                 if (*patternp == NULLCHAR)
2148                   continue;
2149                 else
2150                   return FALSE;
2151             } else {
2152                 *matchp++ = *bufp++;
2153                 continue;
2154             }
2155         }
2156         if (*patternp != *bufp) return FALSE;
2157         patternp++;
2158         bufp++;
2159     }
2160 }
2161
2162 void
2163 SendToPlayer(data, length)
2164      char *data;
2165      int length;
2166 {
2167     int error, outCount;
2168     outCount = OutputToProcess(NoProc, data, length, &error);
2169     if (outCount < length) {
2170         DisplayFatalError(_("Error writing to display"), error, 1);
2171     }
2172 }
2173
2174 void
2175 PackHolding(packed, holding)
2176      char packed[];
2177      char *holding;
2178 {
2179     char *p = holding;
2180     char *q = packed;
2181     int runlength = 0;
2182     int curr = 9999;
2183     do {
2184         if (*p == curr) {
2185             runlength++;
2186         } else {
2187             switch (runlength) {
2188               case 0:
2189                 break;
2190               case 1:
2191                 *q++ = curr;
2192                 break;
2193               case 2:
2194                 *q++ = curr;
2195                 *q++ = curr;
2196                 break;
2197               default:
2198                 sprintf(q, "%d", runlength);
2199                 while (*q) q++;
2200                 *q++ = curr;
2201                 break;
2202             }
2203             runlength = 1;
2204             curr = *p;
2205         }
2206     } while (*p++);
2207     *q = NULLCHAR;
2208 }
2209
2210 /* Telnet protocol requests from the front end */
2211 void
2212 TelnetRequest(ddww, option)
2213      unsigned char ddww, option;
2214 {
2215     unsigned char msg[3];
2216     int outCount, outError;
2217
2218     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2219
2220     if (appData.debugMode) {
2221         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2222         switch (ddww) {
2223           case TN_DO:
2224             ddwwStr = "DO";
2225             break;
2226           case TN_DONT:
2227             ddwwStr = "DONT";
2228             break;
2229           case TN_WILL:
2230             ddwwStr = "WILL";
2231             break;
2232           case TN_WONT:
2233             ddwwStr = "WONT";
2234             break;
2235           default:
2236             ddwwStr = buf1;
2237             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2238             break;
2239         }
2240         switch (option) {
2241           case TN_ECHO:
2242             optionStr = "ECHO";
2243             break;
2244           default:
2245             optionStr = buf2;
2246             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2247             break;
2248         }
2249         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250     }
2251     msg[0] = TN_IAC;
2252     msg[1] = ddww;
2253     msg[2] = option;
2254     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2255     if (outCount < 3) {
2256         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2257     }
2258 }
2259
2260 void
2261 DoEcho()
2262 {
2263     if (!appData.icsActive) return;
2264     TelnetRequest(TN_DO, TN_ECHO);
2265 }
2266
2267 void
2268 DontEcho()
2269 {
2270     if (!appData.icsActive) return;
2271     TelnetRequest(TN_DONT, TN_ECHO);
2272 }
2273
2274 void
2275 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2276 {
2277     /* put the holdings sent to us by the server on the board holdings area */
2278     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2279     char p;
2280     ChessSquare piece;
2281
2282     if(gameInfo.holdingsWidth < 2)  return;
2283     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2284         return; // prevent overwriting by pre-board holdings
2285
2286     if( (int)lowestPiece >= BlackPawn ) {
2287         holdingsColumn = 0;
2288         countsColumn = 1;
2289         holdingsStartRow = BOARD_HEIGHT-1;
2290         direction = -1;
2291     } else {
2292         holdingsColumn = BOARD_WIDTH-1;
2293         countsColumn = BOARD_WIDTH-2;
2294         holdingsStartRow = 0;
2295         direction = 1;
2296     }
2297
2298     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2299         board[i][holdingsColumn] = EmptySquare;
2300         board[i][countsColumn]   = (ChessSquare) 0;
2301     }
2302     while( (p=*holdings++) != NULLCHAR ) {
2303         piece = CharToPiece( ToUpper(p) );
2304         if(piece == EmptySquare) continue;
2305         /*j = (int) piece - (int) WhitePawn;*/
2306         j = PieceToNumber(piece);
2307         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2308         if(j < 0) continue;               /* should not happen */
2309         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2310         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2311         board[holdingsStartRow+j*direction][countsColumn]++;
2312     }
2313 }
2314
2315
2316 void
2317 VariantSwitch(Board board, VariantClass newVariant)
2318 {
2319    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2320    static Board oldBoard;
2321
2322    startedFromPositionFile = FALSE;
2323    if(gameInfo.variant == newVariant) return;
2324
2325    /* [HGM] This routine is called each time an assignment is made to
2326     * gameInfo.variant during a game, to make sure the board sizes
2327     * are set to match the new variant. If that means adding or deleting
2328     * holdings, we shift the playing board accordingly
2329     * This kludge is needed because in ICS observe mode, we get boards
2330     * of an ongoing game without knowing the variant, and learn about the
2331     * latter only later. This can be because of the move list we requested,
2332     * in which case the game history is refilled from the beginning anyway,
2333     * but also when receiving holdings of a crazyhouse game. In the latter
2334     * case we want to add those holdings to the already received position.
2335     */
2336
2337
2338    if (appData.debugMode) {
2339      fprintf(debugFP, "Switch board from %s to %s\n",
2340              VariantName(gameInfo.variant), VariantName(newVariant));
2341      setbuf(debugFP, NULL);
2342    }
2343    shuffleOpenings = 0;       /* [HGM] shuffle */
2344    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2345    switch(newVariant)
2346      {
2347      case VariantShogi:
2348        newWidth = 9;  newHeight = 9;
2349        gameInfo.holdingsSize = 7;
2350      case VariantBughouse:
2351      case VariantCrazyhouse:
2352        newHoldingsWidth = 2; break;
2353      case VariantGreat:
2354        newWidth = 10;
2355      case VariantSuper:
2356        newHoldingsWidth = 2;
2357        gameInfo.holdingsSize = 8;
2358        break;
2359      case VariantGothic:
2360      case VariantCapablanca:
2361      case VariantCapaRandom:
2362        newWidth = 10;
2363      default:
2364        newHoldingsWidth = gameInfo.holdingsSize = 0;
2365      };
2366
2367    if(newWidth  != gameInfo.boardWidth  ||
2368       newHeight != gameInfo.boardHeight ||
2369       newHoldingsWidth != gameInfo.holdingsWidth ) {
2370
2371      /* shift position to new playing area, if needed */
2372      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2373        for(i=0; i<BOARD_HEIGHT; i++)
2374          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2375            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2376              board[i][j];
2377        for(i=0; i<newHeight; i++) {
2378          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2379          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2380        }
2381      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2382        for(i=0; i<BOARD_HEIGHT; i++)
2383          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2384            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2385              board[i][j];
2386      }
2387      gameInfo.boardWidth  = newWidth;
2388      gameInfo.boardHeight = newHeight;
2389      gameInfo.holdingsWidth = newHoldingsWidth;
2390      gameInfo.variant = newVariant;
2391      InitDrawingSizes(-2, 0);
2392    } else gameInfo.variant = newVariant;
2393    CopyBoard(oldBoard, board);   // remember correctly formatted board
2394      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2395    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2396 }
2397
2398 static int loggedOn = FALSE;
2399
2400 /*-- Game start info cache: --*/
2401 int gs_gamenum;
2402 char gs_kind[MSG_SIZ];
2403 static char player1Name[128] = "";
2404 static char player2Name[128] = "";
2405 static char cont_seq[] = "\n\\   ";
2406 static int player1Rating = -1;
2407 static int player2Rating = -1;
2408 /*----------------------------*/
2409
2410 ColorClass curColor = ColorNormal;
2411 int suppressKibitz = 0;
2412
2413 // [HGM] seekgraph
2414 Boolean soughtPending = FALSE;
2415 Boolean seekGraphUp;
2416 #define MAX_SEEK_ADS 200
2417 #define SQUARE 0x80
2418 char *seekAdList[MAX_SEEK_ADS];
2419 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2420 float tcList[MAX_SEEK_ADS];
2421 char colorList[MAX_SEEK_ADS];
2422 int nrOfSeekAds = 0;
2423 int minRating = 1010, maxRating = 2800;
2424 int hMargin = 10, vMargin = 20, h, w;
2425 extern int squareSize, lineGap;
2426
2427 void
2428 PlotSeekAd(int i)
2429 {
2430         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2431         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2432         if(r < minRating+100 && r >=0 ) r = minRating+100;
2433         if(r > maxRating) r = maxRating;
2434         if(tc < 1.) tc = 1.;
2435         if(tc > 95.) tc = 95.;
2436         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2437         y = ((double)r - minRating)/(maxRating - minRating)
2438             * (h-vMargin-squareSize/8-1) + vMargin;
2439         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2440         if(strstr(seekAdList[i], " u ")) color = 1;
2441         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2442            !strstr(seekAdList[i], "bullet") &&
2443            !strstr(seekAdList[i], "blitz") &&
2444            !strstr(seekAdList[i], "standard") ) color = 2;
2445         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2446         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2447 }
2448
2449 void
2450 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2451 {
2452         char buf[MSG_SIZ], *ext = "";
2453         VariantClass v = StringToVariant(type);
2454         if(strstr(type, "wild")) {
2455             ext = type + 4; // append wild number
2456             if(v == VariantFischeRandom) type = "chess960"; else
2457             if(v == VariantLoadable) type = "setup"; else
2458             type = VariantName(v);
2459         }
2460         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2461         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2462             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2463             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2464             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2465             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2466             seekNrList[nrOfSeekAds] = nr;
2467             zList[nrOfSeekAds] = 0;
2468             seekAdList[nrOfSeekAds++] = StrSave(buf);
2469             if(plot) PlotSeekAd(nrOfSeekAds-1);
2470         }
2471 }
2472
2473 void
2474 EraseSeekDot(int i)
2475 {
2476     int x = xList[i], y = yList[i], d=squareSize/4, k;
2477     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2478     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2479     // now replot every dot that overlapped
2480     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2481         int xx = xList[k], yy = yList[k];
2482         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2483             DrawSeekDot(xx, yy, colorList[k]);
2484     }
2485 }
2486
2487 void
2488 RemoveSeekAd(int nr)
2489 {
2490         int i;
2491         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2492             EraseSeekDot(i);
2493             if(seekAdList[i]) free(seekAdList[i]);
2494             seekAdList[i] = seekAdList[--nrOfSeekAds];
2495             seekNrList[i] = seekNrList[nrOfSeekAds];
2496             ratingList[i] = ratingList[nrOfSeekAds];
2497             colorList[i]  = colorList[nrOfSeekAds];
2498             tcList[i] = tcList[nrOfSeekAds];
2499             xList[i]  = xList[nrOfSeekAds];
2500             yList[i]  = yList[nrOfSeekAds];
2501             zList[i]  = zList[nrOfSeekAds];
2502             seekAdList[nrOfSeekAds] = NULL;
2503             break;
2504         }
2505 }
2506
2507 Boolean
2508 MatchSoughtLine(char *line)
2509 {
2510     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2511     int nr, base, inc, u=0; char dummy;
2512
2513     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2514        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2515        (u=1) &&
2516        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2517         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2518         // match: compact and save the line
2519         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520         return TRUE;
2521     }
2522     return FALSE;
2523 }
2524
2525 int
2526 DrawSeekGraph()
2527 {
2528     int i;
2529     if(!seekGraphUp) return FALSE;
2530     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2531     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2532
2533     DrawSeekBackground(0, 0, w, h);
2534     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2535     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2536     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2537         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2538         yy = h-1-yy;
2539         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2540         if(i%500 == 0) {
2541             char buf[MSG_SIZ];
2542             snprintf(buf, MSG_SIZ, "%d", i);
2543             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2544         }
2545     }
2546     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2547     for(i=1; i<100; i+=(i<10?1:5)) {
2548         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2549         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2550         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2551             char buf[MSG_SIZ];
2552             snprintf(buf, MSG_SIZ, "%d", i);
2553             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2554         }
2555     }
2556     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2557     return TRUE;
2558 }
2559
2560 int SeekGraphClick(ClickType click, int x, int y, int moving)
2561 {
2562     static int lastDown = 0, displayed = 0, lastSecond;
2563     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2564         if(click == Release || moving) return FALSE;
2565         nrOfSeekAds = 0;
2566         soughtPending = TRUE;
2567         SendToICS(ics_prefix);
2568         SendToICS("sought\n"); // should this be "sought all"?
2569     } else { // issue challenge based on clicked ad
2570         int dist = 10000; int i, closest = 0, second = 0;
2571         for(i=0; i<nrOfSeekAds; i++) {
2572             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2573             if(d < dist) { dist = d; closest = i; }
2574             second += (d - zList[i] < 120); // count in-range ads
2575             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2576         }
2577         if(dist < 120) {
2578             char buf[MSG_SIZ];
2579             second = (second > 1);
2580             if(displayed != closest || second != lastSecond) {
2581                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2582                 lastSecond = second; displayed = closest;
2583             }
2584             if(click == Press) {
2585                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2586                 lastDown = closest;
2587                 return TRUE;
2588             } // on press 'hit', only show info
2589             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2590             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2591             SendToICS(ics_prefix);
2592             SendToICS(buf);
2593             return TRUE; // let incoming board of started game pop down the graph
2594         } else if(click == Release) { // release 'miss' is ignored
2595             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2596             if(moving == 2) { // right up-click
2597                 nrOfSeekAds = 0; // refresh graph
2598                 soughtPending = TRUE;
2599                 SendToICS(ics_prefix);
2600                 SendToICS("sought\n"); // should this be "sought all"?
2601             }
2602             return TRUE;
2603         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2604         // press miss or release hit 'pop down' seek graph
2605         seekGraphUp = FALSE;
2606         DrawPosition(TRUE, NULL);
2607     }
2608     return TRUE;
2609 }
2610
2611 void
2612 read_from_ics(isr, closure, data, count, error)
2613      InputSourceRef isr;
2614      VOIDSTAR closure;
2615      char *data;
2616      int count;
2617      int error;
2618 {
2619 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2620 #define STARTED_NONE 0
2621 #define STARTED_MOVES 1
2622 #define STARTED_BOARD 2
2623 #define STARTED_OBSERVE 3
2624 #define STARTED_HOLDINGS 4
2625 #define STARTED_CHATTER 5
2626 #define STARTED_COMMENT 6
2627 #define STARTED_MOVES_NOHIDE 7
2628
2629     static int started = STARTED_NONE;
2630     static char parse[20000];
2631     static int parse_pos = 0;
2632     static char buf[BUF_SIZE + 1];
2633     static int firstTime = TRUE, intfSet = FALSE;
2634     static ColorClass prevColor = ColorNormal;
2635     static int savingComment = FALSE;
2636     static int cmatch = 0; // continuation sequence match
2637     char *bp;
2638     char str[MSG_SIZ];
2639     int i, oldi;
2640     int buf_len;
2641     int next_out;
2642     int tkind;
2643     int backup;    /* [DM] For zippy color lines */
2644     char *p;
2645     char talker[MSG_SIZ]; // [HGM] chat
2646     int channel;
2647
2648     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2649
2650     if (appData.debugMode) {
2651       if (!error) {
2652         fprintf(debugFP, "<ICS: ");
2653         show_bytes(debugFP, data, count);
2654         fprintf(debugFP, "\n");
2655       }
2656     }
2657
2658     if (appData.debugMode) { int f = forwardMostMove;
2659         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2660                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2661                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2662     }
2663     if (count > 0) {
2664         /* If last read ended with a partial line that we couldn't parse,
2665            prepend it to the new read and try again. */
2666         if (leftover_len > 0) {
2667             for (i=0; i<leftover_len; i++)
2668               buf[i] = buf[leftover_start + i];
2669         }
2670
2671     /* copy new characters into the buffer */
2672     bp = buf + leftover_len;
2673     buf_len=leftover_len;
2674     for (i=0; i<count; i++)
2675     {
2676         // ignore these
2677         if (data[i] == '\r')
2678             continue;
2679
2680         // join lines split by ICS?
2681         if (!appData.noJoin)
2682         {
2683             /*
2684                 Joining just consists of finding matches against the
2685                 continuation sequence, and discarding that sequence
2686                 if found instead of copying it.  So, until a match
2687                 fails, there's nothing to do since it might be the
2688                 complete sequence, and thus, something we don't want
2689                 copied.
2690             */
2691             if (data[i] == cont_seq[cmatch])
2692             {
2693                 cmatch++;
2694                 if (cmatch == strlen(cont_seq))
2695                 {
2696                     cmatch = 0; // complete match.  just reset the counter
2697
2698                     /*
2699                         it's possible for the ICS to not include the space
2700                         at the end of the last word, making our [correct]
2701                         join operation fuse two separate words.  the server
2702                         does this when the space occurs at the width setting.
2703                     */
2704                     if (!buf_len || buf[buf_len-1] != ' ')
2705                     {
2706                         *bp++ = ' ';
2707                         buf_len++;
2708                     }
2709                 }
2710                 continue;
2711             }
2712             else if (cmatch)
2713             {
2714                 /*
2715                     match failed, so we have to copy what matched before
2716                     falling through and copying this character.  In reality,
2717                     this will only ever be just the newline character, but
2718                     it doesn't hurt to be precise.
2719                 */
2720                 strncpy(bp, cont_seq, cmatch);
2721                 bp += cmatch;
2722                 buf_len += cmatch;
2723                 cmatch = 0;
2724             }
2725         }
2726
2727         // copy this char
2728         *bp++ = data[i];
2729         buf_len++;
2730     }
2731
2732         buf[buf_len] = NULLCHAR;
2733 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734         next_out = 0;
2735         leftover_start = 0;
2736
2737         i = 0;
2738         while (i < buf_len) {
2739             /* Deal with part of the TELNET option negotiation
2740                protocol.  We refuse to do anything beyond the
2741                defaults, except that we allow the WILL ECHO option,
2742                which ICS uses to turn off password echoing when we are
2743                directly connected to it.  We reject this option
2744                if localLineEditing mode is on (always on in xboard)
2745                and we are talking to port 23, which might be a real
2746                telnet server that will try to keep WILL ECHO on permanently.
2747              */
2748             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2749                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2750                 unsigned char option;
2751                 oldi = i;
2752                 switch ((unsigned char) buf[++i]) {
2753                   case TN_WILL:
2754                     if (appData.debugMode)
2755                       fprintf(debugFP, "\n<WILL ");
2756                     switch (option = (unsigned char) buf[++i]) {
2757                       case TN_ECHO:
2758                         if (appData.debugMode)
2759                           fprintf(debugFP, "ECHO ");
2760                         /* Reply only if this is a change, according
2761                            to the protocol rules. */
2762                         if (remoteEchoOption) break;
2763                         if (appData.localLineEditing &&
2764                             atoi(appData.icsPort) == TN_PORT) {
2765                             TelnetRequest(TN_DONT, TN_ECHO);
2766                         } else {
2767                             EchoOff();
2768                             TelnetRequest(TN_DO, TN_ECHO);
2769                             remoteEchoOption = TRUE;
2770                         }
2771                         break;
2772                       default:
2773                         if (appData.debugMode)
2774                           fprintf(debugFP, "%d ", option);
2775                         /* Whatever this is, we don't want it. */
2776                         TelnetRequest(TN_DONT, option);
2777                         break;
2778                     }
2779                     break;
2780                   case TN_WONT:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WONT ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (!remoteEchoOption) break;
2790                         EchoOn();
2791                         TelnetRequest(TN_DONT, TN_ECHO);
2792                         remoteEchoOption = FALSE;
2793                         break;
2794                       default:
2795                         if (appData.debugMode)
2796                           fprintf(debugFP, "%d ", (unsigned char) option);
2797                         /* Whatever this is, it must already be turned
2798                            off, because we never agree to turn on
2799                            anything non-default, so according to the
2800                            protocol rules, we don't reply. */
2801                         break;
2802                     }
2803                     break;
2804                   case TN_DO:
2805                     if (appData.debugMode)
2806                       fprintf(debugFP, "\n<DO ");
2807                     switch (option = (unsigned char) buf[++i]) {
2808                       default:
2809                         /* Whatever this is, we refuse to do it. */
2810                         if (appData.debugMode)
2811                           fprintf(debugFP, "%d ", option);
2812                         TelnetRequest(TN_WONT, option);
2813                         break;
2814                     }
2815                     break;
2816                   case TN_DONT:
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, "\n<DONT ");
2819                     switch (option = (unsigned char) buf[++i]) {
2820                       default:
2821                         if (appData.debugMode)
2822                           fprintf(debugFP, "%d ", option);
2823                         /* Whatever this is, we are already not doing
2824                            it, because we never agree to do anything
2825                            non-default, so according to the protocol
2826                            rules, we don't reply. */
2827                         break;
2828                     }
2829                     break;
2830                   case TN_IAC:
2831                     if (appData.debugMode)
2832                       fprintf(debugFP, "\n<IAC ");
2833                     /* Doubled IAC; pass it through */
2834                     i--;
2835                     break;
2836                   default:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2839                     /* Drop all other telnet commands on the floor */
2840                     break;
2841                 }
2842                 if (oldi > next_out)
2843                   SendToPlayer(&buf[next_out], oldi - next_out);
2844                 if (++i > next_out)
2845                   next_out = i;
2846                 continue;
2847             }
2848
2849             /* OK, this at least will *usually* work */
2850             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2851                 loggedOn = TRUE;
2852             }
2853
2854             if (loggedOn && !intfSet) {
2855                 if (ics_type == ICS_ICC) {
2856                   snprintf(str, MSG_SIZ,
2857                           "/set-quietly interface %s\n/set-quietly style 12\n",
2858                           programVersion);
2859                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2860                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2861                 } else if (ics_type == ICS_CHESSNET) {
2862                   snprintf(str, MSG_SIZ, "/style 12\n");
2863                 } else {
2864                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2865                   strcat(str, programVersion);
2866                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2867                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2868                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2869 #ifdef WIN32
2870                   strcat(str, "$iset nohighlight 1\n");
2871 #endif
2872                   strcat(str, "$iset lock 1\n$style 12\n");
2873                 }
2874                 SendToICS(str);
2875                 NotifyFrontendLogin();
2876                 intfSet = TRUE;
2877             }
2878
2879             if (started == STARTED_COMMENT) {
2880                 /* Accumulate characters in comment */
2881                 parse[parse_pos++] = buf[i];
2882                 if (buf[i] == '\n') {
2883                     parse[parse_pos] = NULLCHAR;
2884                     if(chattingPartner>=0) {
2885                         char mess[MSG_SIZ];
2886                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2887                         OutputChatMessage(chattingPartner, mess);
2888                         chattingPartner = -1;
2889                         next_out = i+1; // [HGM] suppress printing in ICS window
2890                     } else
2891                     if(!suppressKibitz) // [HGM] kibitz
2892                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2893                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2894                         int nrDigit = 0, nrAlph = 0, j;
2895                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2896                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2897                         parse[parse_pos] = NULLCHAR;
2898                         // try to be smart: if it does not look like search info, it should go to
2899                         // ICS interaction window after all, not to engine-output window.
2900                         for(j=0; j<parse_pos; j++) { // count letters and digits
2901                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2902                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2903                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2904                         }
2905                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2906                             int depth=0; float score;
2907                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2908                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2909                                 pvInfoList[forwardMostMove-1].depth = depth;
2910                                 pvInfoList[forwardMostMove-1].score = 100*score;
2911                             }
2912                             OutputKibitz(suppressKibitz, parse);
2913                         } else {
2914                             char tmp[MSG_SIZ];
2915                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2916                             SendToPlayer(tmp, strlen(tmp));
2917                         }
2918                         next_out = i+1; // [HGM] suppress printing in ICS window
2919                     }
2920                     started = STARTED_NONE;
2921                 } else {
2922                     /* Don't match patterns against characters in comment */
2923                     i++;
2924                     continue;
2925                 }
2926             }
2927             if (started == STARTED_CHATTER) {
2928                 if (buf[i] != '\n') {
2929                     /* Don't match patterns against characters in chatter */
2930                     i++;
2931                     continue;
2932                 }
2933                 started = STARTED_NONE;
2934                 if(suppressKibitz) next_out = i+1;
2935             }
2936
2937             /* Kludge to deal with rcmd protocol */
2938             if (firstTime && looking_at(buf, &i, "\001*")) {
2939                 DisplayFatalError(&buf[1], 0, 1);
2940                 continue;
2941             } else {
2942                 firstTime = FALSE;
2943             }
2944
2945             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2946                 ics_type = ICS_ICC;
2947                 ics_prefix = "/";
2948                 if (appData.debugMode)
2949                   fprintf(debugFP, "ics_type %d\n", ics_type);
2950                 continue;
2951             }
2952             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2953                 ics_type = ICS_FICS;
2954                 ics_prefix = "$";
2955                 if (appData.debugMode)
2956                   fprintf(debugFP, "ics_type %d\n", ics_type);
2957                 continue;
2958             }
2959             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2960                 ics_type = ICS_CHESSNET;
2961                 ics_prefix = "/";
2962                 if (appData.debugMode)
2963                   fprintf(debugFP, "ics_type %d\n", ics_type);
2964                 continue;
2965             }
2966
2967             if (!loggedOn &&
2968                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2969                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2970                  looking_at(buf, &i, "will be \"*\""))) {
2971               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2972               continue;
2973             }
2974
2975             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2976               char buf[MSG_SIZ];
2977               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2978               DisplayIcsInteractionTitle(buf);
2979               have_set_title = TRUE;
2980             }
2981
2982             /* skip finger notes */
2983             if (started == STARTED_NONE &&
2984                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2985                  (buf[i] == '1' && buf[i+1] == '0')) &&
2986                 buf[i+2] == ':' && buf[i+3] == ' ') {
2987               started = STARTED_CHATTER;
2988               i += 3;
2989               continue;
2990             }
2991
2992             oldi = i;
2993             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2994             if(appData.seekGraph) {
2995                 if(soughtPending && MatchSoughtLine(buf+i)) {
2996                     i = strstr(buf+i, "rated") - buf;
2997                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2998                     next_out = leftover_start = i;
2999                     started = STARTED_CHATTER;
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3004                         && looking_at(buf, &i, "* ads displayed")) {
3005                     soughtPending = FALSE;
3006                     seekGraphUp = TRUE;
3007                     DrawSeekGraph();
3008                     continue;
3009                 }
3010                 if(appData.autoRefresh) {
3011                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3012                         int s = (ics_type == ICS_ICC); // ICC format differs
3013                         if(seekGraphUp)
3014                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3015                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3016                         looking_at(buf, &i, "*% "); // eat prompt
3017                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3018                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3019                         next_out = i; // suppress
3020                         continue;
3021                     }
3022                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3023                         char *p = star_match[0];
3024                         while(*p) {
3025                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3026                             while(*p && *p++ != ' '); // next
3027                         }
3028                         looking_at(buf, &i, "*% "); // eat prompt
3029                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                         next_out = i;
3031                         continue;
3032                     }
3033                 }
3034             }
3035
3036             /* skip formula vars */
3037             if (started == STARTED_NONE &&
3038                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3039               started = STARTED_CHATTER;
3040               i += 3;
3041               continue;
3042             }
3043
3044             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3045             if (appData.autoKibitz && started == STARTED_NONE &&
3046                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3047                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3048                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3049                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3050                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3051                         suppressKibitz = TRUE;
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i;
3054                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3055                                 && (gameMode == IcsPlayingWhite)) ||
3056                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3057                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3058                             started = STARTED_CHATTER; // own kibitz we simply discard
3059                         else {
3060                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3061                             parse_pos = 0; parse[0] = NULLCHAR;
3062                             savingComment = TRUE;
3063                             suppressKibitz = gameMode != IcsObserving ? 2 :
3064                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3065                         }
3066                         continue;
3067                 } else
3068                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3069                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3070                          && atoi(star_match[0])) {
3071                     // suppress the acknowledgements of our own autoKibitz
3072                     char *p;
3073                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3074                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3075                     SendToPlayer(star_match[0], strlen(star_match[0]));
3076                     if(looking_at(buf, &i, "*% ")) // eat prompt
3077                         suppressKibitz = FALSE;
3078                     next_out = i;
3079                     continue;
3080                 }
3081             } // [HGM] kibitz: end of patch
3082
3083             // [HGM] chat: intercept tells by users for which we have an open chat window
3084             channel = -1;
3085             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3086                                            looking_at(buf, &i, "* whispers:") ||
3087                                            looking_at(buf, &i, "* kibitzes:") ||
3088                                            looking_at(buf, &i, "* shouts:") ||
3089                                            looking_at(buf, &i, "* c-shouts:") ||
3090                                            looking_at(buf, &i, "--> * ") ||
3091                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3092                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3093                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3094                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3095                 int p;
3096                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3097                 chattingPartner = -1;
3098
3099                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3100                 for(p=0; p<MAX_CHAT; p++) {
3101                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3102                     talker[0] = '['; strcat(talker, "] ");
3103                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3104                     chattingPartner = p; break;
3105                     }
3106                 } else
3107                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3108                 for(p=0; p<MAX_CHAT; p++) {
3109                     if(!strcmp("kibitzes", chatPartner[p])) {
3110                         talker[0] = '['; strcat(talker, "] ");
3111                         chattingPartner = p; break;
3112                     }
3113                 } else
3114                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3115                 for(p=0; p<MAX_CHAT; p++) {
3116                     if(!strcmp("whispers", chatPartner[p])) {
3117                         talker[0] = '['; strcat(talker, "] ");
3118                         chattingPartner = p; break;
3119                     }
3120                 } else
3121                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3122                   if(buf[i-8] == '-' && buf[i-3] == 't')
3123                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3124                     if(!strcmp("c-shouts", chatPartner[p])) {
3125                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3126                         chattingPartner = p; break;
3127                     }
3128                   }
3129                   if(chattingPartner < 0)
3130                   for(p=0; p<MAX_CHAT; p++) {
3131                     if(!strcmp("shouts", chatPartner[p])) {
3132                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3133                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3134                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3135                         chattingPartner = p; break;
3136                     }
3137                   }
3138                 }
3139                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3140                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3141                     talker[0] = 0; Colorize(ColorTell, FALSE);
3142                     chattingPartner = p; break;
3143                 }
3144                 if(chattingPartner<0) i = oldi; else {
3145                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3146                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3147                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3148                     started = STARTED_COMMENT;
3149                     parse_pos = 0; parse[0] = NULLCHAR;
3150                     savingComment = 3 + chattingPartner; // counts as TRUE
3151                     suppressKibitz = TRUE;
3152                     continue;
3153                 }
3154             } // [HGM] chat: end of patch
3155
3156           backup = i;
3157             if (appData.zippyTalk || appData.zippyPlay) {
3158                 /* [DM] Backup address for color zippy lines */
3159 #if ZIPPY
3160                if (loggedOn == TRUE)
3161                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3162                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3163 #endif
3164             } // [DM] 'else { ' deleted
3165                 if (
3166                     /* Regular tells and says */
3167                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3168                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3169                     looking_at(buf, &i, "* says: ") ||
3170                     /* Don't color "message" or "messages" output */
3171                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3172                     looking_at(buf, &i, "*. * at *:*: ") ||
3173                     looking_at(buf, &i, "--* (*:*): ") ||
3174                     /* Message notifications (same color as tells) */
3175                     looking_at(buf, &i, "* has left a message ") ||
3176                     looking_at(buf, &i, "* just sent you a message:\n") ||
3177                     /* Whispers and kibitzes */
3178                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3179                     looking_at(buf, &i, "* kibitzes: ") ||
3180                     /* Channel tells */
3181                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3182
3183                   if (tkind == 1 && strchr(star_match[0], ':')) {
3184                       /* Avoid "tells you:" spoofs in channels */
3185                      tkind = 3;
3186                   }
3187                   if (star_match[0][0] == NULLCHAR ||
3188                       strchr(star_match[0], ' ') ||
3189                       (tkind == 3 && strchr(star_match[1], ' '))) {
3190                     /* Reject bogus matches */
3191                     i = oldi;
3192                   } else {
3193                     if (appData.colorize) {
3194                       if (oldi > next_out) {
3195                         SendToPlayer(&buf[next_out], oldi - next_out);
3196                         next_out = oldi;
3197                       }
3198                       switch (tkind) {
3199                       case 1:
3200                         Colorize(ColorTell, FALSE);
3201                         curColor = ColorTell;
3202                         break;
3203                       case 2:
3204                         Colorize(ColorKibitz, FALSE);
3205                         curColor = ColorKibitz;
3206                         break;
3207                       case 3:
3208                         p = strrchr(star_match[1], '(');
3209                         if (p == NULL) {
3210                           p = star_match[1];
3211                         } else {
3212                           p++;
3213                         }
3214                         if (atoi(p) == 1) {
3215                           Colorize(ColorChannel1, FALSE);
3216                           curColor = ColorChannel1;
3217                         } else {
3218                           Colorize(ColorChannel, FALSE);
3219                           curColor = ColorChannel;
3220                         }
3221                         break;
3222                       case 5:
3223                         curColor = ColorNormal;
3224                         break;
3225                       }
3226                     }
3227                     if (started == STARTED_NONE && appData.autoComment &&
3228                         (gameMode == IcsObserving ||
3229                          gameMode == IcsPlayingWhite ||
3230                          gameMode == IcsPlayingBlack)) {
3231                       parse_pos = i - oldi;
3232                       memcpy(parse, &buf[oldi], parse_pos);
3233                       parse[parse_pos] = NULLCHAR;
3234                       started = STARTED_COMMENT;
3235                       savingComment = TRUE;
3236                     } else {
3237                       started = STARTED_CHATTER;
3238                       savingComment = FALSE;
3239                     }
3240                     loggedOn = TRUE;
3241                     continue;
3242                   }
3243                 }
3244
3245                 if (looking_at(buf, &i, "* s-shouts: ") ||
3246                     looking_at(buf, &i, "* c-shouts: ")) {
3247                     if (appData.colorize) {
3248                         if (oldi > next_out) {
3249                             SendToPlayer(&buf[next_out], oldi - next_out);
3250                             next_out = oldi;
3251                         }
3252                         Colorize(ColorSShout, FALSE);
3253                         curColor = ColorSShout;
3254                     }
3255                     loggedOn = TRUE;
3256                     started = STARTED_CHATTER;
3257                     continue;
3258                 }
3259
3260                 if (looking_at(buf, &i, "--->")) {
3261                     loggedOn = TRUE;
3262                     continue;
3263                 }
3264
3265                 if (looking_at(buf, &i, "* shouts: ") ||
3266                     looking_at(buf, &i, "--> ")) {
3267                     if (appData.colorize) {
3268                         if (oldi > next_out) {
3269                             SendToPlayer(&buf[next_out], oldi - next_out);
3270                             next_out = oldi;
3271                         }
3272                         Colorize(ColorShout, FALSE);
3273                         curColor = ColorShout;
3274                     }
3275                     loggedOn = TRUE;
3276                     started = STARTED_CHATTER;
3277                     continue;
3278                 }
3279
3280                 if (looking_at( buf, &i, "Challenge:")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorChallenge, FALSE);
3287                         curColor = ColorChallenge;
3288                     }
3289                     loggedOn = TRUE;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "* offers you") ||
3294                     looking_at(buf, &i, "* offers to be") ||
3295                     looking_at(buf, &i, "* would like to") ||
3296                     looking_at(buf, &i, "* requests to") ||
3297                     looking_at(buf, &i, "Your opponent offers") ||
3298                     looking_at(buf, &i, "Your opponent requests")) {
3299
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorRequest, FALSE);
3306                         curColor = ColorRequest;
3307                     }
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* (*) seeking")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorSeek, FALSE);
3318                         curColor = ColorSeek;
3319                     }
3320                     continue;
3321             }
3322
3323           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3324
3325             if (looking_at(buf, &i, "\\   ")) {
3326                 if (prevColor != ColorNormal) {
3327                     if (oldi > next_out) {
3328                         SendToPlayer(&buf[next_out], oldi - next_out);
3329                         next_out = oldi;
3330                     }
3331                     Colorize(prevColor, TRUE);
3332                     curColor = prevColor;
3333                 }
3334                 if (savingComment) {
3335                     parse_pos = i - oldi;
3336                     memcpy(parse, &buf[oldi], parse_pos);
3337                     parse[parse_pos] = NULLCHAR;
3338                     started = STARTED_COMMENT;
3339                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3340                         chattingPartner = savingComment - 3; // kludge to remember the box
3341                 } else {
3342                     started = STARTED_CHATTER;
3343                 }
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "Black Strength :") ||
3348                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3349                 looking_at(buf, &i, "<10>") ||
3350                 looking_at(buf, &i, "#@#")) {
3351                 /* Wrong board style */
3352                 loggedOn = TRUE;
3353                 SendToICS(ics_prefix);
3354                 SendToICS("set style 12\n");
3355                 SendToICS(ics_prefix);
3356                 SendToICS("refresh\n");
3357                 continue;
3358             }
3359
3360             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3361                 ICSInitScript();
3362                 have_sent_ICS_logon = 1;
3363                 continue;
3364             }
3365
3366             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3367                 (looking_at(buf, &i, "\n<12> ") ||
3368                  looking_at(buf, &i, "<12> "))) {
3369                 loggedOn = TRUE;
3370                 if (oldi > next_out) {
3371                     SendToPlayer(&buf[next_out], oldi - next_out);
3372                 }
3373                 next_out = i;
3374                 started = STARTED_BOARD;
3375                 parse_pos = 0;
3376                 continue;
3377             }
3378
3379             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3380                 looking_at(buf, &i, "<b1> ")) {
3381                 if (oldi > next_out) {
3382                     SendToPlayer(&buf[next_out], oldi - next_out);
3383                 }
3384                 next_out = i;
3385                 started = STARTED_HOLDINGS;
3386                 parse_pos = 0;
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3391                 loggedOn = TRUE;
3392                 /* Header for a move list -- first line */
3393
3394                 switch (ics_getting_history) {
3395                   case H_FALSE:
3396                     switch (gameMode) {
3397                       case IcsIdle:
3398                       case BeginningOfGame:
3399                         /* User typed "moves" or "oldmoves" while we
3400                            were idle.  Pretend we asked for these
3401                            moves and soak them up so user can step
3402                            through them and/or save them.
3403                            */
3404                         Reset(FALSE, TRUE);
3405                         gameMode = IcsObserving;
3406                         ModeHighlight();
3407                         ics_gamenum = -1;
3408                         ics_getting_history = H_GOT_UNREQ_HEADER;
3409                         break;
3410                       case EditGame: /*?*/
3411                       case EditPosition: /*?*/
3412                         /* Should above feature work in these modes too? */
3413                         /* For now it doesn't */
3414                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3415                         break;
3416                       default:
3417                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3418                         break;
3419                     }
3420                     break;
3421                   case H_REQUESTED:
3422                     /* Is this the right one? */
3423                     if (gameInfo.white && gameInfo.black &&
3424                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3425                         strcmp(gameInfo.black, star_match[2]) == 0) {
3426                         /* All is well */
3427                         ics_getting_history = H_GOT_REQ_HEADER;
3428                     }
3429                     break;
3430                   case H_GOT_REQ_HEADER:
3431                   case H_GOT_UNREQ_HEADER:
3432                   case H_GOT_UNWANTED_HEADER:
3433                   case H_GETTING_MOVES:
3434                     /* Should not happen */
3435                     DisplayError(_("Error gathering move list: two headers"), 0);
3436                     ics_getting_history = H_FALSE;
3437                     break;
3438                 }
3439
3440                 /* Save player ratings into gameInfo if needed */
3441                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3442                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3443                     (gameInfo.whiteRating == -1 ||
3444                      gameInfo.blackRating == -1)) {
3445
3446                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3447                     gameInfo.blackRating = string_to_rating(star_match[3]);
3448                     if (appData.debugMode)
3449                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3450                               gameInfo.whiteRating, gameInfo.blackRating);
3451                 }
3452                 continue;
3453             }
3454
3455             if (looking_at(buf, &i,
3456               "* * match, initial time: * minute*, increment: * second")) {
3457                 /* Header for a move list -- second line */
3458                 /* Initial board will follow if this is a wild game */
3459                 if (gameInfo.event != NULL) free(gameInfo.event);
3460                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3461                 gameInfo.event = StrSave(str);
3462                 /* [HGM] we switched variant. Translate boards if needed. */
3463                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3464                 continue;
3465             }
3466
3467             if (looking_at(buf, &i, "Move  ")) {
3468                 /* Beginning of a move list */
3469                 switch (ics_getting_history) {
3470                   case H_FALSE:
3471                     /* Normally should not happen */
3472                     /* Maybe user hit reset while we were parsing */
3473                     break;
3474                   case H_REQUESTED:
3475                     /* Happens if we are ignoring a move list that is not
3476                      * the one we just requested.  Common if the user
3477                      * tries to observe two games without turning off
3478                      * getMoveList */
3479                     break;
3480                   case H_GETTING_MOVES:
3481                     /* Should not happen */
3482                     DisplayError(_("Error gathering move list: nested"), 0);
3483                     ics_getting_history = H_FALSE;
3484                     break;
3485                   case H_GOT_REQ_HEADER:
3486                     ics_getting_history = H_GETTING_MOVES;
3487                     started = STARTED_MOVES;
3488                     parse_pos = 0;
3489                     if (oldi > next_out) {
3490                         SendToPlayer(&buf[next_out], oldi - next_out);
3491                     }
3492                     break;
3493                   case H_GOT_UNREQ_HEADER:
3494                     ics_getting_history = H_GETTING_MOVES;
3495                     started = STARTED_MOVES_NOHIDE;
3496                     parse_pos = 0;
3497                     break;
3498                   case H_GOT_UNWANTED_HEADER:
3499                     ics_getting_history = H_FALSE;
3500                     break;
3501                 }
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "% ") ||
3506                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3507                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3508                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3509                     soughtPending = FALSE;
3510                     seekGraphUp = TRUE;
3511                     DrawSeekGraph();
3512                 }
3513                 if(suppressKibitz) next_out = i;
3514                 savingComment = FALSE;
3515                 suppressKibitz = 0;
3516                 switch (started) {
3517                   case STARTED_MOVES:
3518                   case STARTED_MOVES_NOHIDE:
3519                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3520                     parse[parse_pos + i - oldi] = NULLCHAR;
3521                     ParseGameHistory(parse);
3522 #if ZIPPY
3523                     if (appData.zippyPlay && first.initDone) {
3524                         FeedMovesToProgram(&first, forwardMostMove);
3525                         if (gameMode == IcsPlayingWhite) {
3526                             if (WhiteOnMove(forwardMostMove)) {
3527                                 if (first.sendTime) {
3528                                   if (first.useColors) {
3529                                     SendToProgram("black\n", &first);
3530                                   }
3531                                   SendTimeRemaining(&first, TRUE);
3532                                 }
3533                                 if (first.useColors) {
3534                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3535                                 }
3536                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3537                                 first.maybeThinking = TRUE;
3538                             } else {
3539                                 if (first.usePlayother) {
3540                                   if (first.sendTime) {
3541                                     SendTimeRemaining(&first, TRUE);
3542                                   }
3543                                   SendToProgram("playother\n", &first);
3544                                   firstMove = FALSE;
3545                                 } else {
3546                                   firstMove = TRUE;
3547                                 }
3548                             }
3549                         } else if (gameMode == IcsPlayingBlack) {
3550                             if (!WhiteOnMove(forwardMostMove)) {
3551                                 if (first.sendTime) {
3552                                   if (first.useColors) {
3553                                     SendToProgram("white\n", &first);
3554                                   }
3555                                   SendTimeRemaining(&first, FALSE);
3556                                 }
3557                                 if (first.useColors) {
3558                                   SendToProgram("black\n", &first);
3559                                 }
3560                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3561                                 first.maybeThinking = TRUE;
3562                             } else {
3563                                 if (first.usePlayother) {
3564                                   if (first.sendTime) {
3565                                     SendTimeRemaining(&first, FALSE);
3566                                   }
3567                                   SendToProgram("playother\n", &first);
3568                                   firstMove = FALSE;
3569                                 } else {
3570                                   firstMove = TRUE;
3571                                 }
3572                             }
3573                         }
3574                     }
3575 #endif
3576                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3577                         /* Moves came from oldmoves or moves command
3578                            while we weren't doing anything else.
3579                            */
3580                         currentMove = forwardMostMove;
3581                         ClearHighlights();/*!!could figure this out*/
3582                         flipView = appData.flipView;
3583                         DrawPosition(TRUE, boards[currentMove]);
3584                         DisplayBothClocks();
3585                         snprintf(str, MSG_SIZ, "%s vs. %s",
3586                                 gameInfo.white, gameInfo.black);
3587                         DisplayTitle(str);
3588                         gameMode = IcsIdle;
3589                     } else {
3590                         /* Moves were history of an active game */
3591                         if (gameInfo.resultDetails != NULL) {
3592                             free(gameInfo.resultDetails);
3593                             gameInfo.resultDetails = NULL;
3594                         }
3595                     }
3596                     HistorySet(parseList, backwardMostMove,
3597                                forwardMostMove, currentMove-1);
3598                     DisplayMove(currentMove - 1);
3599                     if (started == STARTED_MOVES) next_out = i;
3600                     started = STARTED_NONE;
3601                     ics_getting_history = H_FALSE;
3602                     break;
3603
3604                   case STARTED_OBSERVE:
3605                     started = STARTED_NONE;
3606                     SendToICS(ics_prefix);
3607                     SendToICS("refresh\n");
3608                     break;
3609
3610                   default:
3611                     break;
3612                 }
3613                 if(bookHit) { // [HGM] book: simulate book reply
3614                     static char bookMove[MSG_SIZ]; // a bit generous?
3615
3616                     programStats.nodes = programStats.depth = programStats.time =
3617                     programStats.score = programStats.got_only_move = 0;
3618                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3619
3620                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3621                     strcat(bookMove, bookHit);
3622                     HandleMachineMove(bookMove, &first);
3623                 }
3624                 continue;
3625             }
3626
3627             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3628                  started == STARTED_HOLDINGS ||
3629                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3630                 /* Accumulate characters in move list or board */
3631                 parse[parse_pos++] = buf[i];
3632             }
3633
3634             /* Start of game messages.  Mostly we detect start of game
3635                when the first board image arrives.  On some versions
3636                of the ICS, though, we need to do a "refresh" after starting
3637                to observe in order to get the current board right away. */
3638             if (looking_at(buf, &i, "Adding game * to observation list")) {
3639                 started = STARTED_OBSERVE;
3640                 continue;
3641             }
3642
3643             /* Handle auto-observe */
3644             if (appData.autoObserve &&
3645                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3646                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3647                 char *player;
3648                 /* Choose the player that was highlighted, if any. */
3649                 if (star_match[0][0] == '\033' ||
3650                     star_match[1][0] != '\033') {
3651                     player = star_match[0];
3652                 } else {
3653                     player = star_match[2];
3654                 }
3655                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3656                         ics_prefix, StripHighlightAndTitle(player));
3657                 SendToICS(str);
3658
3659                 /* Save ratings from notify string */
3660                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3661                 player1Rating = string_to_rating(star_match[1]);
3662                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3663                 player2Rating = string_to_rating(star_match[3]);
3664
3665                 if (appData.debugMode)
3666                   fprintf(debugFP,
3667                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3668                           player1Name, player1Rating,
3669                           player2Name, player2Rating);
3670
3671                 continue;
3672             }
3673
3674             /* Deal with automatic examine mode after a game,
3675                and with IcsObserving -> IcsExamining transition */
3676             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3677                 looking_at(buf, &i, "has made you an examiner of game *")) {
3678
3679                 int gamenum = atoi(star_match[0]);
3680                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3681                     gamenum == ics_gamenum) {
3682                     /* We were already playing or observing this game;
3683                        no need to refetch history */
3684                     gameMode = IcsExamining;
3685                     if (pausing) {
3686                         pauseExamForwardMostMove = forwardMostMove;
3687                     } else if (currentMove < forwardMostMove) {
3688                         ForwardInner(forwardMostMove);
3689                     }
3690                 } else {
3691                     /* I don't think this case really can happen */
3692                     SendToICS(ics_prefix);
3693                     SendToICS("refresh\n");
3694                 }
3695                 continue;
3696             }
3697
3698             /* Error messages */
3699 //          if (ics_user_moved) {
3700             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3701                 if (looking_at(buf, &i, "Illegal move") ||
3702                     looking_at(buf, &i, "Not a legal move") ||
3703                     looking_at(buf, &i, "Your king is in check") ||
3704                     looking_at(buf, &i, "It isn't your turn") ||
3705                     looking_at(buf, &i, "It is not your move")) {
3706                     /* Illegal move */
3707                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3708                         currentMove = forwardMostMove-1;
3709                         DisplayMove(currentMove - 1); /* before DMError */
3710                         DrawPosition(FALSE, boards[currentMove]);
3711                         SwitchClocks(forwardMostMove-1); // [HGM] race
3712                         DisplayBothClocks();
3713                     }
3714                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3715                     ics_user_moved = 0;
3716                     continue;
3717                 }
3718             }
3719
3720             if (looking_at(buf, &i, "still have time") ||
3721                 looking_at(buf, &i, "not out of time") ||
3722                 looking_at(buf, &i, "either player is out of time") ||
3723                 looking_at(buf, &i, "has timeseal; checking")) {
3724                 /* We must have called his flag a little too soon */
3725                 whiteFlag = blackFlag = FALSE;
3726                 continue;
3727             }
3728
3729             if (looking_at(buf, &i, "added * seconds to") ||
3730                 looking_at(buf, &i, "seconds were added to")) {
3731                 /* Update the clocks */
3732                 SendToICS(ics_prefix);
3733                 SendToICS("refresh\n");
3734                 continue;
3735             }
3736
3737             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3738                 ics_clock_paused = TRUE;
3739                 StopClocks();
3740                 continue;
3741             }
3742
3743             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3744                 ics_clock_paused = FALSE;
3745                 StartClocks();
3746                 continue;
3747             }
3748
3749             /* Grab player ratings from the Creating: message.
3750                Note we have to check for the special case when
3751                the ICS inserts things like [white] or [black]. */
3752             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3753                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3754                 /* star_matches:
3755                    0    player 1 name (not necessarily white)
3756                    1    player 1 rating
3757                    2    empty, white, or black (IGNORED)
3758                    3    player 2 name (not necessarily black)
3759                    4    player 2 rating
3760
3761                    The names/ratings are sorted out when the game
3762                    actually starts (below).
3763                 */
3764                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3765                 player1Rating = string_to_rating(star_match[1]);
3766                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3767                 player2Rating = string_to_rating(star_match[4]);
3768
3769                 if (appData.debugMode)
3770                   fprintf(debugFP,
3771                           "Ratings from 'Creating:' %s %d, %s %d\n",
3772                           player1Name, player1Rating,
3773                           player2Name, player2Rating);
3774
3775                 continue;
3776             }
3777
3778             /* Improved generic start/end-of-game messages */
3779             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3780                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3781                 /* If tkind == 0: */
3782                 /* star_match[0] is the game number */
3783                 /*           [1] is the white player's name */
3784                 /*           [2] is the black player's name */
3785                 /* For end-of-game: */
3786                 /*           [3] is the reason for the game end */
3787                 /*           [4] is a PGN end game-token, preceded by " " */
3788                 /* For start-of-game: */
3789                 /*           [3] begins with "Creating" or "Continuing" */
3790                 /*           [4] is " *" or empty (don't care). */
3791                 int gamenum = atoi(star_match[0]);
3792                 char *whitename, *blackname, *why, *endtoken;
3793                 ChessMove endtype = EndOfFile;
3794
3795                 if (tkind == 0) {
3796                   whitename = star_match[1];
3797                   blackname = star_match[2];
3798                   why = star_match[3];
3799                   endtoken = star_match[4];
3800                 } else {
3801                   whitename = star_match[1];
3802                   blackname = star_match[3];
3803                   why = star_match[5];
3804                   endtoken = star_match[6];
3805                 }
3806
3807                 /* Game start messages */
3808                 if (strncmp(why, "Creating ", 9) == 0 ||
3809                     strncmp(why, "Continuing ", 11) == 0) {
3810                     gs_gamenum = gamenum;
3811                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3812                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3813 #if ZIPPY
3814                     if (appData.zippyPlay) {
3815                         ZippyGameStart(whitename, blackname);
3816                     }
3817 #endif /*ZIPPY*/
3818                     partnerBoardValid = FALSE; // [HGM] bughouse
3819                     continue;
3820                 }
3821
3822                 /* Game end messages */
3823                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3824                     ics_gamenum != gamenum) {
3825                     continue;
3826                 }
3827                 while (endtoken[0] == ' ') endtoken++;
3828                 switch (endtoken[0]) {
3829                   case '*':
3830                   default:
3831                     endtype = GameUnfinished;
3832                     break;
3833                   case '0':
3834                     endtype = BlackWins;
3835                     break;
3836                   case '1':
3837                     if (endtoken[1] == '/')
3838                       endtype = GameIsDrawn;
3839                     else
3840                       endtype = WhiteWins;
3841                     break;
3842                 }
3843                 GameEnds(endtype, why, GE_ICS);
3844 #if ZIPPY
3845                 if (appData.zippyPlay && first.initDone) {
3846                     ZippyGameEnd(endtype, why);
3847                     if (first.pr == NULL) {
3848                       /* Start the next process early so that we'll
3849                          be ready for the next challenge */
3850                       StartChessProgram(&first);
3851                     }
3852                     /* Send "new" early, in case this command takes
3853                        a long time to finish, so that we'll be ready
3854                        for the next challenge. */
3855                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3856                     Reset(TRUE, TRUE);
3857                 }
3858 #endif /*ZIPPY*/
3859                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3860                 continue;
3861             }
3862
3863             if (looking_at(buf, &i, "Removing game * from observation") ||
3864                 looking_at(buf, &i, "no longer observing game *") ||
3865                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3866                 if (gameMode == IcsObserving &&
3867                     atoi(star_match[0]) == ics_gamenum)
3868                   {
3869                       /* icsEngineAnalyze */
3870                       if (appData.icsEngineAnalyze) {
3871                             ExitAnalyzeMode();
3872                             ModeHighlight();
3873                       }
3874                       StopClocks();
3875                       gameMode = IcsIdle;
3876                       ics_gamenum = -1;
3877                       ics_user_moved = FALSE;
3878                   }
3879                 continue;
3880             }
3881
3882             if (looking_at(buf, &i, "no longer examining game *")) {
3883                 if (gameMode == IcsExamining &&
3884                     atoi(star_match[0]) == ics_gamenum)
3885                   {
3886                       gameMode = IcsIdle;
3887                       ics_gamenum = -1;
3888                       ics_user_moved = FALSE;
3889                   }
3890                 continue;
3891             }
3892
3893             /* Advance leftover_start past any newlines we find,
3894                so only partial lines can get reparsed */
3895             if (looking_at(buf, &i, "\n")) {
3896                 prevColor = curColor;
3897                 if (curColor != ColorNormal) {
3898                     if (oldi > next_out) {
3899                         SendToPlayer(&buf[next_out], oldi - next_out);
3900                         next_out = oldi;
3901                     }
3902                     Colorize(ColorNormal, FALSE);
3903                     curColor = ColorNormal;
3904                 }
3905                 if (started == STARTED_BOARD) {
3906                     started = STARTED_NONE;
3907                     parse[parse_pos] = NULLCHAR;
3908                     ParseBoard12(parse);
3909                     ics_user_moved = 0;
3910
3911                     /* Send premove here */
3912                     if (appData.premove) {
3913                       char str[MSG_SIZ];
3914                       if (currentMove == 0 &&
3915                           gameMode == IcsPlayingWhite &&
3916                           appData.premoveWhite) {
3917                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3918                         if (appData.debugMode)
3919                           fprintf(debugFP, "Sending premove:\n");
3920                         SendToICS(str);
3921                       } else if (currentMove == 1 &&
3922                                  gameMode == IcsPlayingBlack &&
3923                                  appData.premoveBlack) {
3924                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3925                         if (appData.debugMode)
3926                           fprintf(debugFP, "Sending premove:\n");
3927                         SendToICS(str);
3928                       } else if (gotPremove) {
3929                         gotPremove = 0;
3930                         ClearPremoveHighlights();
3931                         if (appData.debugMode)
3932                           fprintf(debugFP, "Sending premove:\n");
3933                           UserMoveEvent(premoveFromX, premoveFromY,
3934                                         premoveToX, premoveToY,
3935                                         premovePromoChar);
3936                       }
3937                     }
3938
3939                     /* Usually suppress following prompt */
3940                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3941                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3942                         if (looking_at(buf, &i, "*% ")) {
3943                             savingComment = FALSE;
3944                             suppressKibitz = 0;
3945                         }
3946                     }
3947                     next_out = i;
3948                 } else if (started == STARTED_HOLDINGS) {
3949                     int gamenum;
3950                     char new_piece[MSG_SIZ];
3951                     started = STARTED_NONE;
3952                     parse[parse_pos] = NULLCHAR;
3953                     if (appData.debugMode)
3954                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3955                                                         parse, currentMove);
3956                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3957                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3958                         if (gameInfo.variant == VariantNormal) {
3959                           /* [HGM] We seem to switch variant during a game!
3960                            * Presumably no holdings were displayed, so we have
3961                            * to move the position two files to the right to
3962                            * create room for them!
3963                            */
3964                           VariantClass newVariant;
3965                           switch(gameInfo.boardWidth) { // base guess on board width
3966                                 case 9:  newVariant = VariantShogi; break;
3967                                 case 10: newVariant = VariantGreat; break;
3968                                 default: newVariant = VariantCrazyhouse; break;
3969                           }
3970                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3971                           /* Get a move list just to see the header, which
3972                              will tell us whether this is really bug or zh */
3973                           if (ics_getting_history == H_FALSE) {
3974                             ics_getting_history = H_REQUESTED;
3975                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3976                             SendToICS(str);
3977                           }
3978                         }
3979                         new_piece[0] = NULLCHAR;
3980                         sscanf(parse, "game %d white [%s black [%s <- %s",
3981                                &gamenum, white_holding, black_holding,
3982                                new_piece);
3983                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3984                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3985                         /* [HGM] copy holdings to board holdings area */
3986                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3987                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3988                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3989 #if ZIPPY
3990                         if (appData.zippyPlay && first.initDone) {
3991                             ZippyHoldings(white_holding, black_holding,
3992                                           new_piece);
3993                         }
3994 #endif /*ZIPPY*/
3995                         if (tinyLayout || smallLayout) {
3996                             char wh[16], bh[16];
3997                             PackHolding(wh, white_holding);
3998                             PackHolding(bh, black_holding);
3999                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4000                                     gameInfo.white, gameInfo.black);
4001                         } else {
4002                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4003                                     gameInfo.white, white_holding,
4004                                     gameInfo.black, black_holding);
4005                         }
4006                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4007                         DrawPosition(FALSE, boards[currentMove]);
4008                         DisplayTitle(str);
4009                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to partner-board holdings area */
4016                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4017                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4018                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4019                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4020                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4021                       }
4022                     }
4023                     /* Suppress following prompt */
4024                     if (looking_at(buf, &i, "*% ")) {
4025                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4026                         savingComment = FALSE;
4027                         suppressKibitz = 0;
4028                     }
4029                     next_out = i;
4030                 }
4031                 continue;
4032             }
4033
4034             i++;                /* skip unparsed character and loop back */
4035         }
4036
4037         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4038 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4039 //          SendToPlayer(&buf[next_out], i - next_out);
4040             started != STARTED_HOLDINGS && leftover_start > next_out) {
4041             SendToPlayer(&buf[next_out], leftover_start - next_out);
4042             next_out = i;
4043         }
4044
4045         leftover_len = buf_len - leftover_start;
4046         /* if buffer ends with something we couldn't parse,
4047            reparse it after appending the next read */
4048
4049     } else if (count == 0) {
4050         RemoveInputSource(isr);
4051         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4052     } else {
4053         DisplayFatalError(_("Error reading from ICS"), error, 1);
4054     }
4055 }
4056
4057
4058 /* Board style 12 looks like this:
4059
4060    <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
4061
4062  * The "<12> " is stripped before it gets to this routine.  The two
4063  * trailing 0's (flip state and clock ticking) are later addition, and
4064  * some chess servers may not have them, or may have only the first.
4065  * Additional trailing fields may be added in the future.
4066  */
4067
4068 #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"
4069
4070 #define RELATION_OBSERVING_PLAYED    0
4071 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4072 #define RELATION_PLAYING_MYMOVE      1
4073 #define RELATION_PLAYING_NOTMYMOVE  -1
4074 #define RELATION_EXAMINING           2
4075 #define RELATION_ISOLATED_BOARD     -3
4076 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4077
4078 void
4079 ParseBoard12(string)
4080      char *string;
4081 {
4082     GameMode newGameMode;
4083     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4084     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4085     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4086     char to_play, board_chars[200];
4087     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4088     char black[32], white[32];
4089     Board board;
4090     int prevMove = currentMove;
4091     int ticking = 2;
4092     ChessMove moveType;
4093     int fromX, fromY, toX, toY;
4094     char promoChar;
4095     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4096     char *bookHit = NULL; // [HGM] book
4097     Boolean weird = FALSE, reqFlag = FALSE;
4098
4099     fromX = fromY = toX = toY = -1;
4100
4101     newGame = FALSE;
4102
4103     if (appData.debugMode)
4104       fprintf(debugFP, _("Parsing board: %s\n"), string);
4105
4106     move_str[0] = NULLCHAR;
4107     elapsed_time[0] = NULLCHAR;
4108     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4109         int  i = 0, j;
4110         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4111             if(string[i] == ' ') { ranks++; files = 0; }
4112             else files++;
4113             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4114             i++;
4115         }
4116         for(j = 0; j <i; j++) board_chars[j] = string[j];
4117         board_chars[i] = '\0';
4118         string += i + 1;
4119     }
4120     n = sscanf(string, PATTERN, &to_play, &double_push,
4121                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4122                &gamenum, white, black, &relation, &basetime, &increment,
4123                &white_stren, &black_stren, &white_time, &black_time,
4124                &moveNum, str, elapsed_time, move_str, &ics_flip,
4125                &ticking);
4126
4127     if (n < 21) {
4128         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4129         DisplayError(str, 0);
4130         return;
4131     }
4132
4133     /* Convert the move number to internal form */
4134     moveNum = (moveNum - 1) * 2;
4135     if (to_play == 'B') moveNum++;
4136     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4137       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4138                         0, 1);
4139       return;
4140     }
4141
4142     switch (relation) {
4143       case RELATION_OBSERVING_PLAYED:
4144       case RELATION_OBSERVING_STATIC:
4145         if (gamenum == -1) {
4146             /* Old ICC buglet */
4147             relation = RELATION_OBSERVING_STATIC;
4148         }
4149         newGameMode = IcsObserving;
4150         break;
4151       case RELATION_PLAYING_MYMOVE:
4152       case RELATION_PLAYING_NOTMYMOVE:
4153         newGameMode =
4154           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4155             IcsPlayingWhite : IcsPlayingBlack;
4156         break;
4157       case RELATION_EXAMINING:
4158         newGameMode = IcsExamining;
4159         break;
4160       case RELATION_ISOLATED_BOARD:
4161       default:
4162         /* Just display this board.  If user was doing something else,
4163            we will forget about it until the next board comes. */
4164         newGameMode = IcsIdle;
4165         break;
4166       case RELATION_STARTING_POSITION:
4167         newGameMode = gameMode;
4168         break;
4169     }
4170
4171     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4172          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4173       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4174       char *toSqr;
4175       for (k = 0; k < ranks; k++) {
4176         for (j = 0; j < files; j++)
4177           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4178         if(gameInfo.holdingsWidth > 1) {
4179              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4180              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4181         }
4182       }
4183       CopyBoard(partnerBoard, board);
4184       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4185         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4186         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4187       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4188       if(toSqr = strchr(str, '-')) {
4189         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4190         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4191       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4192       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4193       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4194       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4195       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4196       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4197                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4198       DisplayMessage(partnerStatus, "");
4199         partnerBoardValid = TRUE;
4200       return;
4201     }
4202
4203     /* Modify behavior for initial board display on move listing
4204        of wild games.
4205        */
4206     switch (ics_getting_history) {
4207       case H_FALSE:
4208       case H_REQUESTED:
4209         break;
4210       case H_GOT_REQ_HEADER:
4211       case H_GOT_UNREQ_HEADER:
4212         /* This is the initial position of the current game */
4213         gamenum = ics_gamenum;
4214         moveNum = 0;            /* old ICS bug workaround */
4215         if (to_play == 'B') {
4216           startedFromSetupPosition = TRUE;
4217           blackPlaysFirst = TRUE;
4218           moveNum = 1;
4219           if (forwardMostMove == 0) forwardMostMove = 1;
4220           if (backwardMostMove == 0) backwardMostMove = 1;
4221           if (currentMove == 0) currentMove = 1;
4222         }
4223         newGameMode = gameMode;
4224         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4225         break;
4226       case H_GOT_UNWANTED_HEADER:
4227         /* This is an initial board that we don't want */
4228         return;
4229       case H_GETTING_MOVES:
4230         /* Should not happen */
4231         DisplayError(_("Error gathering move list: extra board"), 0);
4232         ics_getting_history = H_FALSE;
4233         return;
4234     }
4235
4236    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4237                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4238      /* [HGM] We seem to have switched variant unexpectedly
4239       * Try to guess new variant from board size
4240       */
4241           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4242           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4243           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4244           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4245           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4246           if(!weird) newVariant = VariantNormal;
4247           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4248           /* Get a move list just to see the header, which
4249              will tell us whether this is really bug or zh */
4250           if (ics_getting_history == H_FALSE) {
4251             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4252             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4253             SendToICS(str);
4254           }
4255     }
4256
4257     /* Take action if this is the first board of a new game, or of a
4258        different game than is currently being displayed.  */
4259     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4260         relation == RELATION_ISOLATED_BOARD) {
4261
4262         /* Forget the old game and get the history (if any) of the new one */
4263         if (gameMode != BeginningOfGame) {
4264           Reset(TRUE, TRUE);
4265         }
4266         newGame = TRUE;
4267         if (appData.autoRaiseBoard) BoardToTop();
4268         prevMove = -3;
4269         if (gamenum == -1) {
4270             newGameMode = IcsIdle;
4271         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4272                    appData.getMoveList && !reqFlag) {
4273             /* Need to get game history */
4274             ics_getting_history = H_REQUESTED;
4275             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4276             SendToICS(str);
4277         }
4278
4279         /* Initially flip the board to have black on the bottom if playing
4280            black or if the ICS flip flag is set, but let the user change
4281            it with the Flip View button. */
4282         flipView = appData.autoFlipView ?
4283           (newGameMode == IcsPlayingBlack) || ics_flip :
4284           appData.flipView;
4285
4286         /* Done with values from previous mode; copy in new ones */
4287         gameMode = newGameMode;
4288         ModeHighlight();
4289         ics_gamenum = gamenum;
4290         if (gamenum == gs_gamenum) {
4291             int klen = strlen(gs_kind);
4292             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4293             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4294             gameInfo.event = StrSave(str);
4295         } else {
4296             gameInfo.event = StrSave("ICS game");
4297         }
4298         gameInfo.site = StrSave(appData.icsHost);
4299         gameInfo.date = PGNDate();
4300         gameInfo.round = StrSave("-");
4301         gameInfo.white = StrSave(white);
4302         gameInfo.black = StrSave(black);
4303         timeControl = basetime * 60 * 1000;
4304         timeControl_2 = 0;
4305         timeIncrement = increment * 1000;
4306         movesPerSession = 0;
4307         gameInfo.timeControl = TimeControlTagValue();
4308         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4309   if (appData.debugMode) {
4310     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4311     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4312     setbuf(debugFP, NULL);
4313   }
4314
4315         gameInfo.outOfBook = NULL;
4316
4317         /* Do we have the ratings? */
4318         if (strcmp(player1Name, white) == 0 &&
4319             strcmp(player2Name, black) == 0) {
4320             if (appData.debugMode)
4321               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4322                       player1Rating, player2Rating);
4323             gameInfo.whiteRating = player1Rating;
4324             gameInfo.blackRating = player2Rating;
4325         } else if (strcmp(player2Name, white) == 0 &&
4326                    strcmp(player1Name, black) == 0) {
4327             if (appData.debugMode)
4328               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4329                       player2Rating, player1Rating);
4330             gameInfo.whiteRating = player2Rating;
4331             gameInfo.blackRating = player1Rating;
4332         }
4333         player1Name[0] = player2Name[0] = NULLCHAR;
4334
4335         /* Silence shouts if requested */
4336         if (appData.quietPlay &&
4337             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4338             SendToICS(ics_prefix);
4339             SendToICS("set shout 0\n");
4340         }
4341     }
4342
4343     /* Deal with midgame name changes */
4344     if (!newGame) {
4345         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4346             if (gameInfo.white) free(gameInfo.white);
4347             gameInfo.white = StrSave(white);
4348         }
4349         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4350             if (gameInfo.black) free(gameInfo.black);
4351             gameInfo.black = StrSave(black);
4352         }
4353     }
4354
4355     /* Throw away game result if anything actually changes in examine mode */
4356     if (gameMode == IcsExamining && !newGame) {
4357         gameInfo.result = GameUnfinished;
4358         if (gameInfo.resultDetails != NULL) {
4359             free(gameInfo.resultDetails);
4360             gameInfo.resultDetails = NULL;
4361         }
4362     }
4363
4364     /* In pausing && IcsExamining mode, we ignore boards coming
4365        in if they are in a different variation than we are. */
4366     if (pauseExamInvalid) return;
4367     if (pausing && gameMode == IcsExamining) {
4368         if (moveNum <= pauseExamForwardMostMove) {
4369             pauseExamInvalid = TRUE;
4370             forwardMostMove = pauseExamForwardMostMove;
4371             return;
4372         }
4373     }
4374
4375   if (appData.debugMode) {
4376     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4377   }
4378     /* Parse the board */
4379     for (k = 0; k < ranks; k++) {
4380       for (j = 0; j < files; j++)
4381         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4382       if(gameInfo.holdingsWidth > 1) {
4383            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4384            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4385       }
4386     }
4387     CopyBoard(boards[moveNum], board);
4388     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4389     if (moveNum == 0) {
4390         startedFromSetupPosition =
4391           !CompareBoards(board, initialPosition);
4392         if(startedFromSetupPosition)
4393             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4394     }
4395
4396     /* [HGM] Set castling rights. Take the outermost Rooks,
4397        to make it also work for FRC opening positions. Note that board12
4398        is really defective for later FRC positions, as it has no way to
4399        indicate which Rook can castle if they are on the same side of King.
4400        For the initial position we grant rights to the outermost Rooks,
4401        and remember thos rights, and we then copy them on positions
4402        later in an FRC game. This means WB might not recognize castlings with
4403        Rooks that have moved back to their original position as illegal,
4404        but in ICS mode that is not its job anyway.
4405     */
4406     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4407     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4408
4409         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4410             if(board[0][i] == WhiteRook) j = i;
4411         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4412         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4413             if(board[0][i] == WhiteRook) j = i;
4414         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4415         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4416             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4417         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4418         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4419             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4420         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4421
4422         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4423         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4424             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4425         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4426             if(board[BOARD_HEIGHT-1][k] == bKing)
4427                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4428         if(gameInfo.variant == VariantTwoKings) {
4429             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4430             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4431             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4432         }
4433     } else { int r;
4434         r = boards[moveNum][CASTLING][0] = initialRights[0];
4435         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4436         r = boards[moveNum][CASTLING][1] = initialRights[1];
4437         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4438         r = boards[moveNum][CASTLING][3] = initialRights[3];
4439         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4440         r = boards[moveNum][CASTLING][4] = initialRights[4];
4441         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4442         /* wildcastle kludge: always assume King has rights */
4443         r = boards[moveNum][CASTLING][2] = initialRights[2];
4444         r = boards[moveNum][CASTLING][5] = initialRights[5];
4445     }
4446     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4447     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4448
4449
4450     if (ics_getting_history == H_GOT_REQ_HEADER ||
4451         ics_getting_history == H_GOT_UNREQ_HEADER) {
4452         /* This was an initial position from a move list, not
4453            the current position */
4454         return;
4455     }
4456
4457     /* Update currentMove and known move number limits */
4458     newMove = newGame || moveNum > forwardMostMove;
4459
4460     if (newGame) {
4461         forwardMostMove = backwardMostMove = currentMove = moveNum;
4462         if (gameMode == IcsExamining && moveNum == 0) {
4463           /* Workaround for ICS limitation: we are not told the wild
4464              type when starting to examine a game.  But if we ask for
4465              the move list, the move list header will tell us */
4466             ics_getting_history = H_REQUESTED;
4467             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4468             SendToICS(str);
4469         }
4470     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4471                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4472 #if ZIPPY
4473         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4474         /* [HGM] applied this also to an engine that is silently watching        */
4475         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4476             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4477             gameInfo.variant == currentlyInitializedVariant) {
4478           takeback = forwardMostMove - moveNum;
4479           for (i = 0; i < takeback; i++) {
4480             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4481             SendToProgram("undo\n", &first);
4482           }
4483         }
4484 #endif
4485
4486         forwardMostMove = moveNum;
4487         if (!pausing || currentMove > forwardMostMove)
4488           currentMove = forwardMostMove;
4489     } else {
4490         /* New part of history that is not contiguous with old part */
4491         if (pausing && gameMode == IcsExamining) {
4492             pauseExamInvalid = TRUE;
4493             forwardMostMove = pauseExamForwardMostMove;
4494             return;
4495         }
4496         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4497 #if ZIPPY
4498             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4499                 // [HGM] when we will receive the move list we now request, it will be
4500                 // fed to the engine from the first move on. So if the engine is not
4501                 // in the initial position now, bring it there.
4502                 InitChessProgram(&first, 0);
4503             }
4504 #endif
4505             ics_getting_history = H_REQUESTED;
4506             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4507             SendToICS(str);
4508         }
4509         forwardMostMove = backwardMostMove = currentMove = moveNum;
4510     }
4511
4512     /* Update the clocks */
4513     if (strchr(elapsed_time, '.')) {
4514       /* Time is in ms */
4515       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4516       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4517     } else {
4518       /* Time is in seconds */
4519       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4520       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4521     }
4522
4523
4524 #if ZIPPY
4525     if (appData.zippyPlay && newGame &&
4526         gameMode != IcsObserving && gameMode != IcsIdle &&
4527         gameMode != IcsExamining)
4528       ZippyFirstBoard(moveNum, basetime, increment);
4529 #endif
4530
4531     /* Put the move on the move list, first converting
4532        to canonical algebraic form. */
4533     if (moveNum > 0) {
4534   if (appData.debugMode) {
4535     if (appData.debugMode) { int f = forwardMostMove;
4536         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4537                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4538                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4539     }
4540     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4541     fprintf(debugFP, "moveNum = %d\n", moveNum);
4542     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4543     setbuf(debugFP, NULL);
4544   }
4545         if (moveNum <= backwardMostMove) {
4546             /* We don't know what the board looked like before
4547                this move.  Punt. */
4548           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4549             strcat(parseList[moveNum - 1], " ");
4550             strcat(parseList[moveNum - 1], elapsed_time);
4551             moveList[moveNum - 1][0] = NULLCHAR;
4552         } else if (strcmp(move_str, "none") == 0) {
4553             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4554             /* Again, we don't know what the board looked like;
4555                this is really the start of the game. */
4556             parseList[moveNum - 1][0] = NULLCHAR;
4557             moveList[moveNum - 1][0] = NULLCHAR;
4558             backwardMostMove = moveNum;
4559             startedFromSetupPosition = TRUE;
4560             fromX = fromY = toX = toY = -1;
4561         } else {
4562           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4563           //                 So we parse the long-algebraic move string in stead of the SAN move
4564           int valid; char buf[MSG_SIZ], *prom;
4565
4566           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4567                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4568           // str looks something like "Q/a1-a2"; kill the slash
4569           if(str[1] == '/')
4570             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4571           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4572           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4573                 strcat(buf, prom); // long move lacks promo specification!
4574           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4575                 if(appData.debugMode)
4576                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4577                 safeStrCpy(move_str, buf, MSG_SIZ);
4578           }
4579           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4580                                 &fromX, &fromY, &toX, &toY, &promoChar)
4581                || ParseOneMove(buf, moveNum - 1, &moveType,
4582                                 &fromX, &fromY, &toX, &toY, &promoChar);
4583           // end of long SAN patch
4584           if (valid) {
4585             (void) CoordsToAlgebraic(boards[moveNum - 1],
4586                                      PosFlags(moveNum - 1),
4587                                      fromY, fromX, toY, toX, promoChar,
4588                                      parseList[moveNum-1]);
4589             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4590               case MT_NONE:
4591               case MT_STALEMATE:
4592               default:
4593                 break;
4594               case MT_CHECK:
4595                 if(gameInfo.variant != VariantShogi)
4596                     strcat(parseList[moveNum - 1], "+");
4597                 break;
4598               case MT_CHECKMATE:
4599               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4600                 strcat(parseList[moveNum - 1], "#");
4601                 break;
4602             }
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             /* currentMoveString is set as a side-effect of ParseOneMove */
4606             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4607             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4608             strcat(moveList[moveNum - 1], "\n");
4609
4610             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4611                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4612               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4613                 ChessSquare old, new = boards[moveNum][k][j];
4614                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4615                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4616                   if(old == new) continue;
4617                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4618                   else if(new == WhiteWazir || new == BlackWazir) {
4619                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4620                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4621                       else boards[moveNum][k][j] = old; // preserve type of Gold
4622                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4623                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4624               }
4625           } else {
4626             /* Move from ICS was illegal!?  Punt. */
4627             if (appData.debugMode) {
4628               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4629               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4630             }
4631             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4632             strcat(parseList[moveNum - 1], " ");
4633             strcat(parseList[moveNum - 1], elapsed_time);
4634             moveList[moveNum - 1][0] = NULLCHAR;
4635             fromX = fromY = toX = toY = -1;
4636           }
4637         }
4638   if (appData.debugMode) {
4639     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4640     setbuf(debugFP, NULL);
4641   }
4642
4643 #if ZIPPY
4644         /* Send move to chess program (BEFORE animating it). */
4645         if (appData.zippyPlay && !newGame && newMove &&
4646            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4647
4648             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4649                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4650                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4651                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4652                             move_str);
4653                     DisplayError(str, 0);
4654                 } else {
4655                     if (first.sendTime) {
4656                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4657                     }
4658                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4659                     if (firstMove && !bookHit) {
4660                         firstMove = FALSE;
4661                         if (first.useColors) {
4662                           SendToProgram(gameMode == IcsPlayingWhite ?
4663                                         "white\ngo\n" :
4664                                         "black\ngo\n", &first);
4665                         } else {
4666                           SendToProgram("go\n", &first);
4667                         }
4668                         first.maybeThinking = TRUE;
4669                     }
4670                 }
4671             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4672               if (moveList[moveNum - 1][0] == NULLCHAR) {
4673                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4674                 DisplayError(str, 0);
4675               } else {
4676                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4677                 SendMoveToProgram(moveNum - 1, &first);
4678               }
4679             }
4680         }
4681 #endif
4682     }
4683
4684     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4685         /* If move comes from a remote source, animate it.  If it
4686            isn't remote, it will have already been animated. */
4687         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4688             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4689         }
4690         if (!pausing && appData.highlightLastMove) {
4691             SetHighlights(fromX, fromY, toX, toY);
4692         }
4693     }
4694
4695     /* Start the clocks */
4696     whiteFlag = blackFlag = FALSE;
4697     appData.clockMode = !(basetime == 0 && increment == 0);
4698     if (ticking == 0) {
4699       ics_clock_paused = TRUE;
4700       StopClocks();
4701     } else if (ticking == 1) {
4702       ics_clock_paused = FALSE;
4703     }
4704     if (gameMode == IcsIdle ||
4705         relation == RELATION_OBSERVING_STATIC ||
4706         relation == RELATION_EXAMINING ||
4707         ics_clock_paused)
4708       DisplayBothClocks();
4709     else
4710       StartClocks();
4711
4712     /* Display opponents and material strengths */
4713     if (gameInfo.variant != VariantBughouse &&
4714         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4715         if (tinyLayout || smallLayout) {
4716             if(gameInfo.variant == VariantNormal)
4717               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4718                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4719                     basetime, increment);
4720             else
4721               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4722                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4723                     basetime, increment, (int) gameInfo.variant);
4724         } else {
4725             if(gameInfo.variant == VariantNormal)
4726               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4727                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4728                     basetime, increment);
4729             else
4730               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4731                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4732                     basetime, increment, VariantName(gameInfo.variant));
4733         }
4734         DisplayTitle(str);
4735   if (appData.debugMode) {
4736     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4737   }
4738     }
4739
4740
4741     /* Display the board */
4742     if (!pausing && !appData.noGUI) {
4743
4744       if (appData.premove)
4745           if (!gotPremove ||
4746              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4747              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4748               ClearPremoveHighlights();
4749
4750       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4751         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4752       DrawPosition(j, boards[currentMove]);
4753
4754       DisplayMove(moveNum - 1);
4755       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4756             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4757               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4758         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4759       }
4760     }
4761
4762     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4763 #if ZIPPY
4764     if(bookHit) { // [HGM] book: simulate book reply
4765         static char bookMove[MSG_SIZ]; // a bit generous?
4766
4767         programStats.nodes = programStats.depth = programStats.time =
4768         programStats.score = programStats.got_only_move = 0;
4769         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4770
4771         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4772         strcat(bookMove, bookHit);
4773         HandleMachineMove(bookMove, &first);
4774     }
4775 #endif
4776 }
4777
4778 void
4779 GetMoveListEvent()
4780 {
4781     char buf[MSG_SIZ];
4782     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4783         ics_getting_history = H_REQUESTED;
4784         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4785         SendToICS(buf);
4786     }
4787 }
4788
4789 void
4790 AnalysisPeriodicEvent(force)
4791      int force;
4792 {
4793     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4794          && !force) || !appData.periodicUpdates)
4795       return;
4796
4797     /* Send . command to Crafty to collect stats */
4798     SendToProgram(".\n", &first);
4799
4800     /* Don't send another until we get a response (this makes
4801        us stop sending to old Crafty's which don't understand
4802        the "." command (sending illegal cmds resets node count & time,
4803        which looks bad)) */
4804     programStats.ok_to_send = 0;
4805 }
4806
4807 void ics_update_width(new_width)
4808         int new_width;
4809 {
4810         ics_printf("set width %d\n", new_width);
4811 }
4812
4813 void
4814 SendMoveToProgram(moveNum, cps)
4815      int moveNum;
4816      ChessProgramState *cps;
4817 {
4818     char buf[MSG_SIZ];
4819
4820     if (cps->useUsermove) {
4821       SendToProgram("usermove ", cps);
4822     }
4823     if (cps->useSAN) {
4824       char *space;
4825       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4826         int len = space - parseList[moveNum];
4827         memcpy(buf, parseList[moveNum], len);
4828         buf[len++] = '\n';
4829         buf[len] = NULLCHAR;
4830       } else {
4831         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4832       }
4833       SendToProgram(buf, cps);
4834     } else {
4835       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4836         AlphaRank(moveList[moveNum], 4);
4837         SendToProgram(moveList[moveNum], cps);
4838         AlphaRank(moveList[moveNum], 4); // and back
4839       } else
4840       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4841        * the engine. It would be nice to have a better way to identify castle
4842        * moves here. */
4843       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4844                                                                          && cps->useOOCastle) {
4845         int fromX = moveList[moveNum][0] - AAA;
4846         int fromY = moveList[moveNum][1] - ONE;
4847         int toX = moveList[moveNum][2] - AAA;
4848         int toY = moveList[moveNum][3] - ONE;
4849         if((boards[moveNum][fromY][fromX] == WhiteKing
4850             && boards[moveNum][toY][toX] == WhiteRook)
4851            || (boards[moveNum][fromY][fromX] == BlackKing
4852                && boards[moveNum][toY][toX] == BlackRook)) {
4853           if(toX > fromX) SendToProgram("O-O\n", cps);
4854           else SendToProgram("O-O-O\n", cps);
4855         }
4856         else SendToProgram(moveList[moveNum], cps);
4857       }
4858       else SendToProgram(moveList[moveNum], cps);
4859       /* End of additions by Tord */
4860     }
4861
4862     /* [HGM] setting up the opening has brought engine in force mode! */
4863     /*       Send 'go' if we are in a mode where machine should play. */
4864     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4865         (gameMode == TwoMachinesPlay   ||
4866 #if ZIPPY
4867          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4868 #endif
4869          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4870         SendToProgram("go\n", cps);
4871   if (appData.debugMode) {
4872     fprintf(debugFP, "(extra)\n");
4873   }
4874     }
4875     setboardSpoiledMachineBlack = 0;
4876 }
4877
4878 void
4879 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4880      ChessMove moveType;
4881      int fromX, fromY, toX, toY;
4882      char promoChar;
4883 {
4884     char user_move[MSG_SIZ];
4885
4886     switch (moveType) {
4887       default:
4888         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4889                 (int)moveType, fromX, fromY, toX, toY);
4890         DisplayError(user_move + strlen("say "), 0);
4891         break;
4892       case WhiteKingSideCastle:
4893       case BlackKingSideCastle:
4894       case WhiteQueenSideCastleWild:
4895       case BlackQueenSideCastleWild:
4896       /* PUSH Fabien */
4897       case WhiteHSideCastleFR:
4898       case BlackHSideCastleFR:
4899       /* POP Fabien */
4900         snprintf(user_move, MSG_SIZ, "o-o\n");
4901         break;
4902       case WhiteQueenSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case BlackKingSideCastleWild:
4906       /* PUSH Fabien */
4907       case WhiteASideCastleFR:
4908       case BlackASideCastleFR:
4909       /* POP Fabien */
4910         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4911         break;
4912       case WhiteNonPromotion:
4913       case BlackNonPromotion:
4914         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4915         break;
4916       case WhitePromotion:
4917       case BlackPromotion:
4918         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4919           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4920                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4921                 PieceToChar(WhiteFerz));
4922         else if(gameInfo.variant == VariantGreat)
4923           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4924                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925                 PieceToChar(WhiteMan));
4926         else
4927           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4928                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4929                 promoChar);
4930         break;
4931       case WhiteDrop:
4932       case BlackDrop:
4933       drop:
4934         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4935                  ToUpper(PieceToChar((ChessSquare) fromX)),
4936                  AAA + toX, ONE + toY);
4937         break;
4938       case IllegalMove:  /* could be a variant we don't quite understand */
4939         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4940       case NormalMove:
4941       case WhiteCapturesEnPassant:
4942       case BlackCapturesEnPassant:
4943         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4944                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4945         break;
4946     }
4947     SendToICS(user_move);
4948     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4949         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4950 }
4951
4952 void
4953 UploadGameEvent()
4954 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4955     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4956     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4957     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4958         DisplayError("You cannot do this while you are playing or observing", 0);
4959         return;
4960     }
4961     if(gameMode != IcsExamining) { // is this ever not the case?
4962         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4963
4964         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4965           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4966         } else { // on FICS we must first go to general examine mode
4967           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4968         }
4969         if(gameInfo.variant != VariantNormal) {
4970             // try figure out wild number, as xboard names are not always valid on ICS
4971             for(i=1; i<=36; i++) {
4972               snprintf(buf, MSG_SIZ, "wild/%d", i);
4973                 if(StringToVariant(buf) == gameInfo.variant) break;
4974             }
4975             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4976             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4977             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4978         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4979         SendToICS(ics_prefix);
4980         SendToICS(buf);
4981         if(startedFromSetupPosition || backwardMostMove != 0) {
4982           fen = PositionToFEN(backwardMostMove, NULL);
4983           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4984             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4985             SendToICS(buf);
4986           } else { // FICS: everything has to set by separate bsetup commands
4987             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4988             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4989             SendToICS(buf);
4990             if(!WhiteOnMove(backwardMostMove)) {
4991                 SendToICS("bsetup tomove black\n");
4992             }
4993             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4994             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4995             SendToICS(buf);
4996             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4997             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4998             SendToICS(buf);
4999             i = boards[backwardMostMove][EP_STATUS];
5000             if(i >= 0) { // set e.p.
5001               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5002                 SendToICS(buf);
5003             }
5004             bsetup++;
5005           }
5006         }
5007       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5008             SendToICS("bsetup done\n"); // switch to normal examining.
5009     }
5010     for(i = backwardMostMove; i<last; i++) {
5011         char buf[20];
5012         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5013         SendToICS(buf);
5014     }
5015     SendToICS(ics_prefix);
5016     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5017 }
5018
5019 void
5020 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5021      int rf, ff, rt, ft;
5022      char promoChar;
5023      char move[7];
5024 {
5025     if (rf == DROP_RANK) {
5026       sprintf(move, "%c@%c%c\n",
5027                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5028     } else {
5029         if (promoChar == 'x' || promoChar == NULLCHAR) {
5030           sprintf(move, "%c%c%c%c\n",
5031                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5032         } else {
5033             sprintf(move, "%c%c%c%c%c\n",
5034                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5035         }
5036     }
5037 }
5038
5039 void
5040 ProcessICSInitScript(f)
5041      FILE *f;
5042 {
5043     char buf[MSG_SIZ];
5044
5045     while (fgets(buf, MSG_SIZ, f)) {
5046         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5047     }
5048
5049     fclose(f);
5050 }
5051
5052
5053 static int lastX, lastY, selectFlag, dragging;
5054
5055 void
5056 Sweep(int step)
5057 {
5058     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5059     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5060     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5061     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5062     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5063     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5064     do {
5065         promoSweep -= step;
5066         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5067         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5068         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5069         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5070         if(!step) step = 1;
5071     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5072             appData.testLegality && (promoSweep == king ||
5073             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5074     ChangeDragPiece(promoSweep);
5075 }
5076
5077 int PromoScroll(int x, int y)
5078 {
5079   int step = 0;
5080
5081   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5082   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5083   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5084   if(!step) return FALSE;
5085   lastX = x; lastY = y;
5086   if((promoSweep < BlackPawn) == flipView) step = -step;
5087   if(step > 0) selectFlag = 1;
5088   if(!selectFlag) Sweep(step);
5089   return FALSE;
5090 }
5091
5092 void
5093 NextPiece(int step)
5094 {
5095     ChessSquare piece = boards[currentMove][toY][toX];
5096     do {
5097         pieceSweep -= step;
5098         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5099         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5100         if(!step) step = -1;
5101     } while(PieceToChar(pieceSweep) == '.');
5102     boards[currentMove][toY][toX] = pieceSweep;
5103     DrawPosition(FALSE, boards[currentMove]);
5104     boards[currentMove][toY][toX] = piece;
5105 }
5106 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5107 void
5108 AlphaRank(char *move, int n)
5109 {
5110 //    char *p = move, c; int x, y;
5111
5112     if (appData.debugMode) {
5113         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5114     }
5115
5116     if(move[1]=='*' &&
5117        move[2]>='0' && move[2]<='9' &&
5118        move[3]>='a' && move[3]<='x'    ) {
5119         move[1] = '@';
5120         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5121         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5122     } else
5123     if(move[0]>='0' && move[0]<='9' &&
5124        move[1]>='a' && move[1]<='x' &&
5125        move[2]>='0' && move[2]<='9' &&
5126        move[3]>='a' && move[3]<='x'    ) {
5127         /* input move, Shogi -> normal */
5128         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5129         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5130         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5131         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5132     } else
5133     if(move[1]=='@' &&
5134        move[3]>='0' && move[3]<='9' &&
5135        move[2]>='a' && move[2]<='x'    ) {
5136         move[1] = '*';
5137         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5138         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5139     } else
5140     if(
5141        move[0]>='a' && move[0]<='x' &&
5142        move[3]>='0' && move[3]<='9' &&
5143        move[2]>='a' && move[2]<='x'    ) {
5144          /* output move, normal -> Shogi */
5145         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5146         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5147         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5149         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5150     }
5151     if (appData.debugMode) {
5152         fprintf(debugFP, "   out = '%s'\n", move);
5153     }
5154 }
5155
5156 char yy_textstr[8000];
5157
5158 /* Parser for moves from gnuchess, ICS, or user typein box */
5159 Boolean
5160 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5161      char *move;
5162      int moveNum;
5163      ChessMove *moveType;
5164      int *fromX, *fromY, *toX, *toY;
5165      char *promoChar;
5166 {
5167     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5168
5169     switch (*moveType) {
5170       case WhitePromotion:
5171       case BlackPromotion:
5172       case WhiteNonPromotion:
5173       case BlackNonPromotion:
5174       case NormalMove:
5175       case WhiteCapturesEnPassant:
5176       case BlackCapturesEnPassant:
5177       case WhiteKingSideCastle:
5178       case WhiteQueenSideCastle:
5179       case BlackKingSideCastle:
5180       case BlackQueenSideCastle:
5181       case WhiteKingSideCastleWild:
5182       case WhiteQueenSideCastleWild:
5183       case BlackKingSideCastleWild:
5184       case BlackQueenSideCastleWild:
5185       /* Code added by Tord: */
5186       case WhiteHSideCastleFR:
5187       case WhiteASideCastleFR:
5188       case BlackHSideCastleFR:
5189       case BlackASideCastleFR:
5190       /* End of code added by Tord */
5191       case IllegalMove:         /* bug or odd chess variant */
5192         *fromX = currentMoveString[0] - AAA;
5193         *fromY = currentMoveString[1] - ONE;
5194         *toX = currentMoveString[2] - AAA;
5195         *toY = currentMoveString[3] - ONE;
5196         *promoChar = currentMoveString[4];
5197         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5198             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5201     }
5202             *fromX = *fromY = *toX = *toY = 0;
5203             return FALSE;
5204         }
5205         if (appData.testLegality) {
5206           return (*moveType != IllegalMove);
5207         } else {
5208           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5209                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5210         }
5211
5212       case WhiteDrop:
5213       case BlackDrop:
5214         *fromX = *moveType == WhiteDrop ?
5215           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5216           (int) CharToPiece(ToLower(currentMoveString[0]));
5217         *fromY = DROP_RANK;
5218         *toX = currentMoveString[2] - AAA;
5219         *toY = currentMoveString[3] - ONE;
5220         *promoChar = NULLCHAR;
5221         return TRUE;
5222
5223       case AmbiguousMove:
5224       case ImpossibleMove:
5225       case EndOfFile:
5226       case ElapsedTime:
5227       case Comment:
5228       case PGNTag:
5229       case NAG:
5230       case WhiteWins:
5231       case BlackWins:
5232       case GameIsDrawn:
5233       default:
5234     if (appData.debugMode) {
5235         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5236     }
5237         /* bug? */
5238         *fromX = *fromY = *toX = *toY = 0;
5239         *promoChar = NULLCHAR;
5240         return FALSE;
5241     }
5242 }
5243
5244 Boolean pushed = FALSE;
5245
5246 void
5247 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5248 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5249   int fromX, fromY, toX, toY; char promoChar;
5250   ChessMove moveType;
5251   Boolean valid;
5252   int nr = 0;
5253
5254   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5255     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5256     pushed = TRUE;
5257   }
5258   endPV = forwardMostMove;
5259   do {
5260     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5261     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5262     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5263 if(appData.debugMode){
5264 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);
5265 }
5266     if(!valid && nr == 0 &&
5267        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5268         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5269         // Hande case where played move is different from leading PV move
5270         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5271         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5272         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5273         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5274           endPV += 2; // if position different, keep this
5275           moveList[endPV-1][0] = fromX + AAA;
5276           moveList[endPV-1][1] = fromY + ONE;
5277           moveList[endPV-1][2] = toX + AAA;
5278           moveList[endPV-1][3] = toY + ONE;
5279           parseList[endPV-1][0] = NULLCHAR;
5280           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5281         }
5282       }
5283     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5284     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5285     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5286     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5287         valid++; // allow comments in PV
5288         continue;
5289     }
5290     nr++;
5291     if(endPV+1 > framePtr) break; // no space, truncate
5292     if(!valid) break;
5293     endPV++;
5294     CopyBoard(boards[endPV], boards[endPV-1]);
5295     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5296     moveList[endPV-1][0] = fromX + AAA;
5297     moveList[endPV-1][1] = fromY + ONE;
5298     moveList[endPV-1][2] = toX + AAA;
5299     moveList[endPV-1][3] = toY + ONE;
5300     moveList[endPV-1][4] = promoChar;
5301     moveList[endPV-1][5] = NULLCHAR;
5302     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5303     if(storeComments)
5304         CoordsToAlgebraic(boards[endPV - 1],
5305                              PosFlags(endPV - 1),
5306                              fromY, fromX, toY, toX, promoChar,
5307                              parseList[endPV - 1]);
5308     else
5309         parseList[endPV-1][0] = NULLCHAR;
5310   } while(valid);
5311   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5312   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5313   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5314                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5315   DrawPosition(TRUE, boards[currentMove]);
5316 }
5317
5318 Boolean
5319 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5320 {
5321         int startPV;
5322         char *p;
5323
5324         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5325         lastX = x; lastY = y;
5326         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5327         startPV = index;
5328         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5329         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5330         index = startPV;
5331         do{ while(buf[index] && buf[index] != '\n') index++;
5332         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5333         buf[index] = 0;
5334         ParsePV(buf+startPV, FALSE, !shiftKey);
5335         *start = startPV; *end = index-1;
5336         return TRUE;
5337 }
5338
5339 Boolean
5340 LoadPV(int x, int y)
5341 { // called on right mouse click to load PV
5342   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5343   lastX = x; lastY = y;
5344   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5345   return TRUE;
5346 }
5347
5348 void
5349 UnLoadPV()
5350 {
5351   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5352   if(endPV < 0) return;
5353   endPV = -1;
5354   if(shiftKey && gameMode == AnalyzeMode) {
5355         if(pushed) storedGames--; // abandon shelved tail of original game
5356         pushed = FALSE;
5357         forwardMostMove = currentMove;
5358         currentMove = oldFMM;
5359         ToNrEvent(forwardMostMove);
5360   }
5361   currentMove = forwardMostMove;
5362   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5363   ClearPremoveHighlights();
5364   DrawPosition(TRUE, boards[currentMove]);
5365 }
5366
5367 void
5368 MovePV(int x, int y, int h)
5369 { // step through PV based on mouse coordinates (called on mouse move)
5370   int margin = h>>3, step = 0;
5371
5372   // we must somehow check if right button is still down (might be released off board!)
5373   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5374   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5375   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5376   if(!step) return;
5377   lastX = x; lastY = y;
5378
5379   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5380   if(endPV < 0) return;
5381   if(y < margin) step = 1; else
5382   if(y > h - margin) step = -1;
5383   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5384   currentMove += step;
5385   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5386   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5387                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5388   DrawPosition(FALSE, boards[currentMove]);
5389 }
5390
5391
5392 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5393 // All positions will have equal probability, but the current method will not provide a unique
5394 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5395 #define DARK 1
5396 #define LITE 2
5397 #define ANY 3
5398
5399 int squaresLeft[4];
5400 int piecesLeft[(int)BlackPawn];
5401 int seed, nrOfShuffles;
5402
5403 void GetPositionNumber()
5404 {       // sets global variable seed
5405         int i;
5406
5407         seed = appData.defaultFrcPosition;
5408         if(seed < 0) { // randomize based on time for negative FRC position numbers
5409                 for(i=0; i<50; i++) seed += random();
5410                 seed = random() ^ random() >> 8 ^ random() << 8;
5411                 if(seed<0) seed = -seed;
5412         }
5413 }
5414
5415 int put(Board board, int pieceType, int rank, int n, int shade)
5416 // put the piece on the (n-1)-th empty squares of the given shade
5417 {
5418         int i;
5419
5420         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5421                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5422                         board[rank][i] = (ChessSquare) pieceType;
5423                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5424                         squaresLeft[ANY]--;
5425                         piecesLeft[pieceType]--;
5426                         return i;
5427                 }
5428         }
5429         return -1;
5430 }
5431
5432
5433 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5434 // calculate where the next piece goes, (any empty square), and put it there
5435 {
5436         int i;
5437
5438         i = seed % squaresLeft[shade];
5439         nrOfShuffles *= squaresLeft[shade];
5440         seed /= squaresLeft[shade];
5441         put(board, pieceType, rank, i, shade);
5442 }
5443
5444 void AddTwoPieces(Board board, int pieceType, int rank)
5445 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5446 {
5447         int i, n=squaresLeft[ANY], j=n-1, k;
5448
5449         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5450         i = seed % k;  // pick one
5451         nrOfShuffles *= k;
5452         seed /= k;
5453         while(i >= j) i -= j--;
5454         j = n - 1 - j; i += j;
5455         put(board, pieceType, rank, j, ANY);
5456         put(board, pieceType, rank, i, ANY);
5457 }
5458
5459 void SetUpShuffle(Board board, int number)
5460 {
5461         int i, p, first=1;
5462
5463         GetPositionNumber(); nrOfShuffles = 1;
5464
5465         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5466         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5467         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5468
5469         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5470
5471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5472             p = (int) board[0][i];
5473             if(p < (int) BlackPawn) piecesLeft[p] ++;
5474             board[0][i] = EmptySquare;
5475         }
5476
5477         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5478             // shuffles restricted to allow normal castling put KRR first
5479             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5480                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5481             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5482                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5483             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5484                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5485             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5486                 put(board, WhiteRook, 0, 0, ANY);
5487             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5488         }
5489
5490         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5491             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5492             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5493                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5494                 while(piecesLeft[p] >= 2) {
5495                     AddOnePiece(board, p, 0, LITE);
5496                     AddOnePiece(board, p, 0, DARK);
5497                 }
5498                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5499             }
5500
5501         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5502             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5503             // but we leave King and Rooks for last, to possibly obey FRC restriction
5504             if(p == (int)WhiteRook) continue;
5505             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5506             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5507         }
5508
5509         // now everything is placed, except perhaps King (Unicorn) and Rooks
5510
5511         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5512             // Last King gets castling rights
5513             while(piecesLeft[(int)WhiteUnicorn]) {
5514                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5515                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5516             }
5517
5518             while(piecesLeft[(int)WhiteKing]) {
5519                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5520                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5521             }
5522
5523
5524         } else {
5525             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5526             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5527         }
5528
5529         // Only Rooks can be left; simply place them all
5530         while(piecesLeft[(int)WhiteRook]) {
5531                 i = put(board, WhiteRook, 0, 0, ANY);
5532                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5533                         if(first) {
5534                                 first=0;
5535                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5536                         }
5537                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5538                 }
5539         }
5540         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5541             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5542         }
5543
5544         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5545 }
5546
5547 int SetCharTable( char *table, const char * map )
5548 /* [HGM] moved here from winboard.c because of its general usefulness */
5549 /*       Basically a safe strcpy that uses the last character as King */
5550 {
5551     int result = FALSE; int NrPieces;
5552
5553     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5554                     && NrPieces >= 12 && !(NrPieces&1)) {
5555         int i; /* [HGM] Accept even length from 12 to 34 */
5556
5557         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5558         for( i=0; i<NrPieces/2-1; i++ ) {
5559             table[i] = map[i];
5560             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5561         }
5562         table[(int) WhiteKing]  = map[NrPieces/2-1];
5563         table[(int) BlackKing]  = map[NrPieces-1];
5564
5565         result = TRUE;
5566     }
5567
5568     return result;
5569 }
5570
5571 void Prelude(Board board)
5572 {       // [HGM] superchess: random selection of exo-pieces
5573         int i, j, k; ChessSquare p;
5574         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5575
5576         GetPositionNumber(); // use FRC position number
5577
5578         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5579             SetCharTable(pieceToChar, appData.pieceToCharTable);
5580             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5581                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5582         }
5583
5584         j = seed%4;                 seed /= 4;
5585         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5586         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5587         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5588         j = seed%3 + (seed%3 >= j); seed /= 3;
5589         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5590         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5591         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5592         j = seed%3;                 seed /= 3;
5593         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5594         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5595         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5596         j = seed%2 + (seed%2 >= j); seed /= 2;
5597         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5598         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5599         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5600         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5601         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5602         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5603         put(board, exoPieces[0],    0, 0, ANY);
5604         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5605 }
5606
5607 void
5608 InitPosition(redraw)
5609      int redraw;
5610 {
5611     ChessSquare (* pieces)[BOARD_FILES];
5612     int i, j, pawnRow, overrule,
5613     oldx = gameInfo.boardWidth,
5614     oldy = gameInfo.boardHeight,
5615     oldh = gameInfo.holdingsWidth;
5616     static int oldv;
5617
5618     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5619
5620     /* [AS] Initialize pv info list [HGM] and game status */
5621     {
5622         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5623             pvInfoList[i].depth = 0;
5624             boards[i][EP_STATUS] = EP_NONE;
5625             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5626         }
5627
5628         initialRulePlies = 0; /* 50-move counter start */
5629
5630         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5631         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5632     }
5633
5634
5635     /* [HGM] logic here is completely changed. In stead of full positions */
5636     /* the initialized data only consist of the two backranks. The switch */
5637     /* selects which one we will use, which is than copied to the Board   */
5638     /* initialPosition, which for the rest is initialized by Pawns and    */
5639     /* empty squares. This initial position is then copied to boards[0],  */
5640     /* possibly after shuffling, so that it remains available.            */
5641
5642     gameInfo.holdingsWidth = 0; /* default board sizes */
5643     gameInfo.boardWidth    = 8;
5644     gameInfo.boardHeight   = 8;
5645     gameInfo.holdingsSize  = 0;
5646     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5647     for(i=0; i<BOARD_FILES-2; i++)
5648       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5649     initialPosition[EP_STATUS] = EP_NONE;
5650     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5651     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5652          SetCharTable(pieceNickName, appData.pieceNickNames);
5653     else SetCharTable(pieceNickName, "............");
5654     pieces = FIDEArray;
5655
5656     switch (gameInfo.variant) {
5657     case VariantFischeRandom:
5658       shuffleOpenings = TRUE;
5659     default:
5660       break;
5661     case VariantShatranj:
5662       pieces = ShatranjArray;
5663       nrCastlingRights = 0;
5664       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5665       break;
5666     case VariantMakruk:
5667       pieces = makrukArray;
5668       nrCastlingRights = 0;
5669       startedFromSetupPosition = TRUE;
5670       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5671       break;
5672     case VariantTwoKings:
5673       pieces = twoKingsArray;
5674       break;
5675     case VariantCapaRandom:
5676       shuffleOpenings = TRUE;
5677     case VariantCapablanca:
5678       pieces = CapablancaArray;
5679       gameInfo.boardWidth = 10;
5680       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5681       break;
5682     case VariantGothic:
5683       pieces = GothicArray;
5684       gameInfo.boardWidth = 10;
5685       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5686       break;
5687     case VariantSChess:
5688       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5689       gameInfo.holdingsSize = 7;
5690       break;
5691     case VariantJanus:
5692       pieces = JanusArray;
5693       gameInfo.boardWidth = 10;
5694       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5695       nrCastlingRights = 6;
5696         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5697         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5698         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5699         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5700         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5701         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5702       break;
5703     case VariantFalcon:
5704       pieces = FalconArray;
5705       gameInfo.boardWidth = 10;
5706       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5707       break;
5708     case VariantXiangqi:
5709       pieces = XiangqiArray;
5710       gameInfo.boardWidth  = 9;
5711       gameInfo.boardHeight = 10;
5712       nrCastlingRights = 0;
5713       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5714       break;
5715     case VariantShogi:
5716       pieces = ShogiArray;
5717       gameInfo.boardWidth  = 9;
5718       gameInfo.boardHeight = 9;
5719       gameInfo.holdingsSize = 7;
5720       nrCastlingRights = 0;
5721       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5722       break;
5723     case VariantCourier:
5724       pieces = CourierArray;
5725       gameInfo.boardWidth  = 12;
5726       nrCastlingRights = 0;
5727       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5728       break;
5729     case VariantKnightmate:
5730       pieces = KnightmateArray;
5731       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5732       break;
5733     case VariantSpartan:
5734       pieces = SpartanArray;
5735       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5736       break;
5737     case VariantFairy:
5738       pieces = fairyArray;
5739       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5740       break;
5741     case VariantGreat:
5742       pieces = GreatArray;
5743       gameInfo.boardWidth = 10;
5744       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5745       gameInfo.holdingsSize = 8;
5746       break;
5747     case VariantSuper:
5748       pieces = FIDEArray;
5749       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5750       gameInfo.holdingsSize = 8;
5751       startedFromSetupPosition = TRUE;
5752       break;
5753     case VariantCrazyhouse:
5754     case VariantBughouse:
5755       pieces = FIDEArray;
5756       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5757       gameInfo.holdingsSize = 5;
5758       break;
5759     case VariantWildCastle:
5760       pieces = FIDEArray;
5761       /* !!?shuffle with kings guaranteed to be on d or e file */
5762       shuffleOpenings = 1;
5763       break;
5764     case VariantNoCastle:
5765       pieces = FIDEArray;
5766       nrCastlingRights = 0;
5767       /* !!?unconstrained back-rank shuffle */
5768       shuffleOpenings = 1;
5769       break;
5770     }
5771
5772     overrule = 0;
5773     if(appData.NrFiles >= 0) {
5774         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5775         gameInfo.boardWidth = appData.NrFiles;
5776     }
5777     if(appData.NrRanks >= 0) {
5778         gameInfo.boardHeight = appData.NrRanks;
5779     }
5780     if(appData.holdingsSize >= 0) {
5781         i = appData.holdingsSize;
5782         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5783         gameInfo.holdingsSize = i;
5784     }
5785     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5786     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5787         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5788
5789     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5790     if(pawnRow < 1) pawnRow = 1;
5791     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5792
5793     /* User pieceToChar list overrules defaults */
5794     if(appData.pieceToCharTable != NULL)
5795         SetCharTable(pieceToChar, appData.pieceToCharTable);
5796
5797     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5798
5799         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5800             s = (ChessSquare) 0; /* account holding counts in guard band */
5801         for( i=0; i<BOARD_HEIGHT; i++ )
5802             initialPosition[i][j] = s;
5803
5804         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5805         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5806         initialPosition[pawnRow][j] = WhitePawn;
5807         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5808         if(gameInfo.variant == VariantXiangqi) {
5809             if(j&1) {
5810                 initialPosition[pawnRow][j] =
5811                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5812                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5813                    initialPosition[2][j] = WhiteCannon;
5814                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5815                 }
5816             }
5817         }
5818         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5819     }
5820     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5821
5822             j=BOARD_LEFT+1;
5823             initialPosition[1][j] = WhiteBishop;
5824             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5825             j=BOARD_RGHT-2;
5826             initialPosition[1][j] = WhiteRook;
5827             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5828     }
5829
5830     if( nrCastlingRights == -1) {
5831         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5832         /*       This sets default castling rights from none to normal corners   */
5833         /* Variants with other castling rights must set them themselves above    */
5834         nrCastlingRights = 6;
5835
5836         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5837         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5838         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5839         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5840         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5841         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5842      }
5843
5844      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5845      if(gameInfo.variant == VariantGreat) { // promotion commoners
5846         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5847         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5848         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5849         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5850      }
5851      if( gameInfo.variant == VariantSChess ) {
5852       initialPosition[1][0] = BlackMarshall;
5853       initialPosition[2][0] = BlackAngel;
5854       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5855       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5856       initialPosition[1][1] = initialPosition[2][1] = 
5857       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5858      }
5859   if (appData.debugMode) {
5860     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5861   }
5862     if(shuffleOpenings) {
5863         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5864         startedFromSetupPosition = TRUE;
5865     }
5866     if(startedFromPositionFile) {
5867       /* [HGM] loadPos: use PositionFile for every new game */
5868       CopyBoard(initialPosition, filePosition);
5869       for(i=0; i<nrCastlingRights; i++)
5870           initialRights[i] = filePosition[CASTLING][i];
5871       startedFromSetupPosition = TRUE;
5872     }
5873
5874     CopyBoard(boards[0], initialPosition);
5875
5876     if(oldx != gameInfo.boardWidth ||
5877        oldy != gameInfo.boardHeight ||
5878        oldv != gameInfo.variant ||
5879        oldh != gameInfo.holdingsWidth
5880                                          )
5881             InitDrawingSizes(-2 ,0);
5882
5883     oldv = gameInfo.variant;
5884     if (redraw)
5885       DrawPosition(TRUE, boards[currentMove]);
5886 }
5887
5888 void
5889 SendBoard(cps, moveNum)
5890      ChessProgramState *cps;
5891      int moveNum;
5892 {
5893     char message[MSG_SIZ];
5894
5895     if (cps->useSetboard) {
5896       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5897       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5898       SendToProgram(message, cps);
5899       free(fen);
5900
5901     } else {
5902       ChessSquare *bp;
5903       int i, j;
5904       /* Kludge to set black to move, avoiding the troublesome and now
5905        * deprecated "black" command.
5906        */
5907       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5908         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5909
5910       SendToProgram("edit\n", cps);
5911       SendToProgram("#\n", cps);
5912       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5913         bp = &boards[moveNum][i][BOARD_LEFT];
5914         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5915           if ((int) *bp < (int) BlackPawn) {
5916             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5917                     AAA + j, ONE + i);
5918             if(message[0] == '+' || message[0] == '~') {
5919               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5920                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5921                         AAA + j, ONE + i);
5922             }
5923             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5924                 message[1] = BOARD_RGHT   - 1 - j + '1';
5925                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5926             }
5927             SendToProgram(message, cps);
5928           }
5929         }
5930       }
5931
5932       SendToProgram("c\n", cps);
5933       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5934         bp = &boards[moveNum][i][BOARD_LEFT];
5935         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5936           if (((int) *bp != (int) EmptySquare)
5937               && ((int) *bp >= (int) BlackPawn)) {
5938             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(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(".\n", cps);
5955     }
5956     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5957 }
5958
5959 ChessSquare
5960 DefaultPromoChoice(int white)
5961 {
5962     ChessSquare result;
5963     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5964         result = WhiteFerz; // no choice
5965     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5966         result= WhiteKing; // in Suicide Q is the last thing we want
5967     else if(gameInfo.variant == VariantSpartan)
5968         result = white ? WhiteQueen : WhiteAngel;
5969     else result = WhiteQueen;
5970     if(!white) result = WHITE_TO_BLACK result;
5971     return result;
5972 }
5973
5974 static int autoQueen; // [HGM] oneclick
5975
5976 int
5977 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5978 {
5979     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5980     /* [HGM] add Shogi promotions */
5981     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5982     ChessSquare piece;
5983     ChessMove moveType;
5984     Boolean premove;
5985
5986     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5987     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5988
5989     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5990       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5991         return FALSE;
5992
5993     piece = boards[currentMove][fromY][fromX];
5994     if(gameInfo.variant == VariantShogi) {
5995         promotionZoneSize = BOARD_HEIGHT/3;
5996         highestPromotingPiece = (int)WhiteFerz;
5997     } else if(gameInfo.variant == VariantMakruk) {
5998         promotionZoneSize = 3;
5999     }
6000
6001     // Treat Lance as Pawn when it is not representing Amazon
6002     if(gameInfo.variant != VariantSuper) {
6003         if(piece == WhiteLance) piece = WhitePawn; else
6004         if(piece == BlackLance) piece = BlackPawn;
6005     }
6006
6007     // next weed out all moves that do not touch the promotion zone at all
6008     if((int)piece >= BlackPawn) {
6009         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6010              return FALSE;
6011         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6012     } else {
6013         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6014            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6015     }
6016
6017     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6018
6019     // weed out mandatory Shogi promotions
6020     if(gameInfo.variant == VariantShogi) {
6021         if(piece >= BlackPawn) {
6022             if(toY == 0 && piece == BlackPawn ||
6023                toY == 0 && piece == BlackQueen ||
6024                toY <= 1 && piece == BlackKnight) {
6025                 *promoChoice = '+';
6026                 return FALSE;
6027             }
6028         } else {
6029             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6030                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6031                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6032                 *promoChoice = '+';
6033                 return FALSE;
6034             }
6035         }
6036     }
6037
6038     // weed out obviously illegal Pawn moves
6039     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6040         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6041         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6042         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6043         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6044         // note we are not allowed to test for valid (non-)capture, due to premove
6045     }
6046
6047     // we either have a choice what to promote to, or (in Shogi) whether to promote
6048     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6049         *promoChoice = PieceToChar(BlackFerz);  // no choice
6050         return FALSE;
6051     }
6052     // no sense asking what we must promote to if it is going to explode...
6053     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6054         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6055         return FALSE;
6056     }
6057     // give caller the default choice even if we will not make it
6058     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6059     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6060     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6061                            && gameInfo.variant != VariantShogi
6062                            && gameInfo.variant != VariantSuper) return FALSE;
6063     if(autoQueen) return FALSE; // predetermined
6064
6065     // suppress promotion popup on illegal moves that are not premoves
6066     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6067               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6068     if(appData.testLegality && !premove) {
6069         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6070                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6071         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6072             return FALSE;
6073     }
6074
6075     return TRUE;
6076 }
6077
6078 int
6079 InPalace(row, column)
6080      int row, column;
6081 {   /* [HGM] for Xiangqi */
6082     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6083          column < (BOARD_WIDTH + 4)/2 &&
6084          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6085     return FALSE;
6086 }
6087
6088 int
6089 PieceForSquare (x, y)
6090      int x;
6091      int y;
6092 {
6093   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6094      return -1;
6095   else
6096      return boards[currentMove][y][x];
6097 }
6098
6099 int
6100 OKToStartUserMove(x, y)
6101      int x, y;
6102 {
6103     ChessSquare from_piece;
6104     int white_piece;
6105
6106     if (matchMode) return FALSE;
6107     if (gameMode == EditPosition) return TRUE;
6108
6109     if (x >= 0 && y >= 0)
6110       from_piece = boards[currentMove][y][x];
6111     else
6112       from_piece = EmptySquare;
6113
6114     if (from_piece == EmptySquare) return FALSE;
6115
6116     white_piece = (int)from_piece >= (int)WhitePawn &&
6117       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6118
6119     switch (gameMode) {
6120       case PlayFromGameFile:
6121       case AnalyzeFile:
6122       case TwoMachinesPlay:
6123       case EndOfGame:
6124         return FALSE;
6125
6126       case IcsObserving:
6127       case IcsIdle:
6128         return FALSE;
6129
6130       case MachinePlaysWhite:
6131       case IcsPlayingBlack:
6132         if (appData.zippyPlay) return FALSE;
6133         if (white_piece) {
6134             DisplayMoveError(_("You are playing Black"));
6135             return FALSE;
6136         }
6137         break;
6138
6139       case MachinePlaysBlack:
6140       case IcsPlayingWhite:
6141         if (appData.zippyPlay) return FALSE;
6142         if (!white_piece) {
6143             DisplayMoveError(_("You are playing White"));
6144             return FALSE;
6145         }
6146         break;
6147
6148       case EditGame:
6149         if (!white_piece && WhiteOnMove(currentMove)) {
6150             DisplayMoveError(_("It is White's turn"));
6151             return FALSE;
6152         }
6153         if (white_piece && !WhiteOnMove(currentMove)) {
6154             DisplayMoveError(_("It is Black's turn"));
6155             return FALSE;
6156         }
6157         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6158             /* Editing correspondence game history */
6159             /* Could disallow this or prompt for confirmation */
6160             cmailOldMove = -1;
6161         }
6162         break;
6163
6164       case BeginningOfGame:
6165         if (appData.icsActive) return FALSE;
6166         if (!appData.noChessProgram) {
6167             if (!white_piece) {
6168                 DisplayMoveError(_("You are playing White"));
6169                 return FALSE;
6170             }
6171         }
6172         break;
6173
6174       case Training:
6175         if (!white_piece && WhiteOnMove(currentMove)) {
6176             DisplayMoveError(_("It is White's turn"));
6177             return FALSE;
6178         }
6179         if (white_piece && !WhiteOnMove(currentMove)) {
6180             DisplayMoveError(_("It is Black's turn"));
6181             return FALSE;
6182         }
6183         break;
6184
6185       default:
6186       case IcsExamining:
6187         break;
6188     }
6189     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6190         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6191         && gameMode != AnalyzeFile && gameMode != Training) {
6192         DisplayMoveError(_("Displayed position is not current"));
6193         return FALSE;
6194     }
6195     return TRUE;
6196 }
6197
6198 Boolean
6199 OnlyMove(int *x, int *y, Boolean captures) {
6200     DisambiguateClosure cl;
6201     if (appData.zippyPlay) return FALSE;
6202     switch(gameMode) {
6203       case MachinePlaysBlack:
6204       case IcsPlayingWhite:
6205       case BeginningOfGame:
6206         if(!WhiteOnMove(currentMove)) return FALSE;
6207         break;
6208       case MachinePlaysWhite:
6209       case IcsPlayingBlack:
6210         if(WhiteOnMove(currentMove)) return FALSE;
6211         break;
6212       case EditGame:
6213         break;
6214       default:
6215         return FALSE;
6216     }
6217     cl.pieceIn = EmptySquare;
6218     cl.rfIn = *y;
6219     cl.ffIn = *x;
6220     cl.rtIn = -1;
6221     cl.ftIn = -1;
6222     cl.promoCharIn = NULLCHAR;
6223     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6224     if( cl.kind == NormalMove ||
6225         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6226         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6227         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6228       fromX = cl.ff;
6229       fromY = cl.rf;
6230       *x = cl.ft;
6231       *y = cl.rt;
6232       return TRUE;
6233     }
6234     if(cl.kind != ImpossibleMove) return FALSE;
6235     cl.pieceIn = EmptySquare;
6236     cl.rfIn = -1;
6237     cl.ffIn = -1;
6238     cl.rtIn = *y;
6239     cl.ftIn = *x;
6240     cl.promoCharIn = NULLCHAR;
6241     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6242     if( cl.kind == NormalMove ||
6243         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6244         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6245         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6246       fromX = cl.ff;
6247       fromY = cl.rf;
6248       *x = cl.ft;
6249       *y = cl.rt;
6250       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6251       return TRUE;
6252     }
6253     return FALSE;
6254 }
6255
6256 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6257 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6258 int lastLoadGameUseList = FALSE;
6259 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6260 ChessMove lastLoadGameStart = EndOfFile;
6261
6262 void
6263 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6264      int fromX, fromY, toX, toY;
6265      int promoChar;
6266 {
6267     ChessMove moveType;
6268     ChessSquare pdown, pup;
6269
6270     /* Check if the user is playing in turn.  This is complicated because we
6271        let the user "pick up" a piece before it is his turn.  So the piece he
6272        tried to pick up may have been captured by the time he puts it down!
6273        Therefore we use the color the user is supposed to be playing in this
6274        test, not the color of the piece that is currently on the starting
6275        square---except in EditGame mode, where the user is playing both
6276        sides; fortunately there the capture race can't happen.  (It can
6277        now happen in IcsExamining mode, but that's just too bad.  The user
6278        will get a somewhat confusing message in that case.)
6279        */
6280
6281     switch (gameMode) {
6282       case PlayFromGameFile:
6283       case AnalyzeFile:
6284       case TwoMachinesPlay:
6285       case EndOfGame:
6286       case IcsObserving:
6287       case IcsIdle:
6288         /* We switched into a game mode where moves are not accepted,
6289            perhaps while the mouse button was down. */
6290         return;
6291
6292       case MachinePlaysWhite:
6293         /* User is moving for Black */
6294         if (WhiteOnMove(currentMove)) {
6295             DisplayMoveError(_("It is White's turn"));
6296             return;
6297         }
6298         break;
6299
6300       case MachinePlaysBlack:
6301         /* User is moving for White */
6302         if (!WhiteOnMove(currentMove)) {
6303             DisplayMoveError(_("It is Black's turn"));
6304             return;
6305         }
6306         break;
6307
6308       case EditGame:
6309       case IcsExamining:
6310       case BeginningOfGame:
6311       case AnalyzeMode:
6312       case Training:
6313         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6314         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6315             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6316             /* User is moving for Black */
6317             if (WhiteOnMove(currentMove)) {
6318                 DisplayMoveError(_("It is White's turn"));
6319                 return;
6320             }
6321         } else {
6322             /* User is moving for White */
6323             if (!WhiteOnMove(currentMove)) {
6324                 DisplayMoveError(_("It is Black's turn"));
6325                 return;
6326             }
6327         }
6328         break;
6329
6330       case IcsPlayingBlack:
6331         /* User is moving for Black */
6332         if (WhiteOnMove(currentMove)) {
6333             if (!appData.premove) {
6334                 DisplayMoveError(_("It is White's turn"));
6335             } else if (toX >= 0 && toY >= 0) {
6336                 premoveToX = toX;
6337                 premoveToY = toY;
6338                 premoveFromX = fromX;
6339                 premoveFromY = fromY;
6340                 premovePromoChar = promoChar;
6341                 gotPremove = 1;
6342                 if (appData.debugMode)
6343                     fprintf(debugFP, "Got premove: fromX %d,"
6344                             "fromY %d, toX %d, toY %d\n",
6345                             fromX, fromY, toX, toY);
6346             }
6347             return;
6348         }
6349         break;
6350
6351       case IcsPlayingWhite:
6352         /* User is moving for White */
6353         if (!WhiteOnMove(currentMove)) {
6354             if (!appData.premove) {
6355                 DisplayMoveError(_("It is Black's turn"));
6356             } else if (toX >= 0 && toY >= 0) {
6357                 premoveToX = toX;
6358                 premoveToY = toY;
6359                 premoveFromX = fromX;
6360                 premoveFromY = fromY;
6361                 premovePromoChar = promoChar;
6362                 gotPremove = 1;
6363                 if (appData.debugMode)
6364                     fprintf(debugFP, "Got premove: fromX %d,"
6365                             "fromY %d, toX %d, toY %d\n",
6366                             fromX, fromY, toX, toY);
6367             }
6368             return;
6369         }
6370         break;
6371
6372       default:
6373         break;
6374
6375       case EditPosition:
6376         /* EditPosition, empty square, or different color piece;
6377            click-click move is possible */
6378         if (toX == -2 || toY == -2) {
6379             boards[0][fromY][fromX] = EmptySquare;
6380             DrawPosition(FALSE, boards[currentMove]);
6381             return;
6382         } else if (toX >= 0 && toY >= 0) {
6383             boards[0][toY][toX] = boards[0][fromY][fromX];
6384             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6385                 if(boards[0][fromY][0] != EmptySquare) {
6386                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6387                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6388                 }
6389             } else
6390             if(fromX == BOARD_RGHT+1) {
6391                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6392                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6393                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6394                 }
6395             } else
6396             boards[0][fromY][fromX] = EmptySquare;
6397             DrawPosition(FALSE, boards[currentMove]);
6398             return;
6399         }
6400         return;
6401     }
6402
6403     if(toX < 0 || toY < 0) return;
6404     pdown = boards[currentMove][fromY][fromX];
6405     pup = boards[currentMove][toY][toX];
6406
6407     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6408     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6409          if( pup != EmptySquare ) return;
6410          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6411            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6412                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6413            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6414            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6415            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6416            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6417          fromY = DROP_RANK;
6418     }
6419
6420     /* [HGM] always test for legality, to get promotion info */
6421     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6422                                          fromY, fromX, toY, toX, promoChar);
6423     /* [HGM] but possibly ignore an IllegalMove result */
6424     if (appData.testLegality) {
6425         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6426             DisplayMoveError(_("Illegal move"));
6427             return;
6428         }
6429     }
6430
6431     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6432 }
6433
6434 /* Common tail of UserMoveEvent and DropMenuEvent */
6435 int
6436 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6437      ChessMove moveType;
6438      int fromX, fromY, toX, toY;
6439      /*char*/int promoChar;
6440 {
6441     char *bookHit = 0;
6442
6443     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6444         // [HGM] superchess: suppress promotions to non-available piece
6445         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6446         if(WhiteOnMove(currentMove)) {
6447             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6448         } else {
6449             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6450         }
6451     }
6452
6453     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6454        move type in caller when we know the move is a legal promotion */
6455     if(moveType == NormalMove && promoChar)
6456         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6457
6458     /* [HGM] <popupFix> The following if has been moved here from
6459        UserMoveEvent(). Because it seemed to belong here (why not allow
6460        piece drops in training games?), and because it can only be
6461        performed after it is known to what we promote. */
6462     if (gameMode == Training) {
6463       /* compare the move played on the board to the next move in the
6464        * game. If they match, display the move and the opponent's response.
6465        * If they don't match, display an error message.
6466        */
6467       int saveAnimate;
6468       Board testBoard;
6469       CopyBoard(testBoard, boards[currentMove]);
6470       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6471
6472       if (CompareBoards(testBoard, boards[currentMove+1])) {
6473         ForwardInner(currentMove+1);
6474
6475         /* Autoplay the opponent's response.
6476          * if appData.animate was TRUE when Training mode was entered,
6477          * the response will be animated.
6478          */
6479         saveAnimate = appData.animate;
6480         appData.animate = animateTraining;
6481         ForwardInner(currentMove+1);
6482         appData.animate = saveAnimate;
6483
6484         /* check for the end of the game */
6485         if (currentMove >= forwardMostMove) {
6486           gameMode = PlayFromGameFile;
6487           ModeHighlight();
6488           SetTrainingModeOff();
6489           DisplayInformation(_("End of game"));
6490         }
6491       } else {
6492         DisplayError(_("Incorrect move"), 0);
6493       }
6494       return 1;
6495     }
6496
6497   /* Ok, now we know that the move is good, so we can kill
6498      the previous line in Analysis Mode */
6499   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6500                                 && currentMove < forwardMostMove) {
6501     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6502     else forwardMostMove = currentMove;
6503   }
6504
6505   /* If we need the chess program but it's dead, restart it */
6506   ResurrectChessProgram();
6507
6508   /* A user move restarts a paused game*/
6509   if (pausing)
6510     PauseEvent();
6511
6512   thinkOutput[0] = NULLCHAR;
6513
6514   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6515
6516   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6517     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6518     return 1;
6519   }
6520
6521   if (gameMode == BeginningOfGame) {
6522     if (appData.noChessProgram) {
6523       gameMode = EditGame;
6524       SetGameInfo();
6525     } else {
6526       char buf[MSG_SIZ];
6527       gameMode = MachinePlaysBlack;
6528       StartClocks();
6529       SetGameInfo();
6530       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6531       DisplayTitle(buf);
6532       if (first.sendName) {
6533         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6534         SendToProgram(buf, &first);
6535       }
6536       StartClocks();
6537     }
6538     ModeHighlight();
6539   }
6540
6541   /* Relay move to ICS or chess engine */
6542   if (appData.icsActive) {
6543     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6544         gameMode == IcsExamining) {
6545       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6546         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6547         SendToICS("draw ");
6548         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6549       }
6550       // also send plain move, in case ICS does not understand atomic claims
6551       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6552       ics_user_moved = 1;
6553     }
6554   } else {
6555     if (first.sendTime && (gameMode == BeginningOfGame ||
6556                            gameMode == MachinePlaysWhite ||
6557                            gameMode == MachinePlaysBlack)) {
6558       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6559     }
6560     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6561          // [HGM] book: if program might be playing, let it use book
6562         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6563         first.maybeThinking = TRUE;
6564     } else SendMoveToProgram(forwardMostMove-1, &first);
6565     if (currentMove == cmailOldMove + 1) {
6566       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6567     }
6568   }
6569
6570   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6571
6572   switch (gameMode) {
6573   case EditGame:
6574     if(appData.testLegality)
6575     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6576     case MT_NONE:
6577     case MT_CHECK:
6578       break;
6579     case MT_CHECKMATE:
6580     case MT_STAINMATE:
6581       if (WhiteOnMove(currentMove)) {
6582         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6583       } else {
6584         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6585       }
6586       break;
6587     case MT_STALEMATE:
6588       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6589       break;
6590     }
6591     break;
6592
6593   case MachinePlaysBlack:
6594   case MachinePlaysWhite:
6595     /* disable certain menu options while machine is thinking */
6596     SetMachineThinkingEnables();
6597     break;
6598
6599   default:
6600     break;
6601   }
6602
6603   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6604   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6605
6606   if(bookHit) { // [HGM] book: simulate book reply
6607         static char bookMove[MSG_SIZ]; // a bit generous?
6608
6609         programStats.nodes = programStats.depth = programStats.time =
6610         programStats.score = programStats.got_only_move = 0;
6611         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6612
6613         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6614         strcat(bookMove, bookHit);
6615         HandleMachineMove(bookMove, &first);
6616   }
6617   return 1;
6618 }
6619
6620 void
6621 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6622      Board board;
6623      int flags;
6624      ChessMove kind;
6625      int rf, ff, rt, ft;
6626      VOIDSTAR closure;
6627 {
6628     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6629     Markers *m = (Markers *) closure;
6630     if(rf == fromY && ff == fromX)
6631         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6632                          || kind == WhiteCapturesEnPassant
6633                          || kind == BlackCapturesEnPassant);
6634     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6635 }
6636
6637 void
6638 MarkTargetSquares(int clear)
6639 {
6640   int x, y;
6641   if(!appData.markers || !appData.highlightDragging ||
6642      !appData.testLegality || gameMode == EditPosition) return;
6643   if(clear) {
6644     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6645   } else {
6646     int capt = 0;
6647     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6648     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6649       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6650       if(capt)
6651       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6652     }
6653   }
6654   DrawPosition(TRUE, NULL);
6655 }
6656
6657 int
6658 Explode(Board board, int fromX, int fromY, int toX, int toY)
6659 {
6660     if(gameInfo.variant == VariantAtomic &&
6661        (board[toY][toX] != EmptySquare ||                     // capture?
6662         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6663                          board[fromY][fromX] == BlackPawn   )
6664       )) {
6665         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6666         return TRUE;
6667     }
6668     return FALSE;
6669 }
6670
6671 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6672
6673 int CanPromote(ChessSquare piece, int y)
6674 {
6675         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6676         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6677         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6678            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6679            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6680                                                   gameInfo.variant == VariantMakruk) return FALSE;
6681         return (piece == BlackPawn && y == 1 ||
6682                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6683                 piece == BlackLance && y == 1 ||
6684                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6685 }
6686
6687 void LeftClick(ClickType clickType, int xPix, int yPix)
6688 {
6689     int x, y;
6690     Boolean saveAnimate;
6691     static int second = 0, promotionChoice = 0, clearFlag = 0;
6692     char promoChoice = NULLCHAR;
6693     ChessSquare piece;
6694
6695     if(appData.seekGraph && appData.icsActive && loggedOn &&
6696         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6697         SeekGraphClick(clickType, xPix, yPix, 0);
6698         return;
6699     }
6700
6701     if (clickType == Press) ErrorPopDown();
6702     MarkTargetSquares(1);
6703
6704     x = EventToSquare(xPix, BOARD_WIDTH);
6705     y = EventToSquare(yPix, BOARD_HEIGHT);
6706     if (!flipView && y >= 0) {
6707         y = BOARD_HEIGHT - 1 - y;
6708     }
6709     if (flipView && x >= 0) {
6710         x = BOARD_WIDTH - 1 - x;
6711     }
6712
6713     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6714         defaultPromoChoice = promoSweep;
6715         promoSweep = EmptySquare;   // terminate sweep
6716         promoDefaultAltered = TRUE;
6717         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6718     }
6719
6720     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6721         if(clickType == Release) return; // ignore upclick of click-click destination
6722         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6723         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6724         if(gameInfo.holdingsWidth &&
6725                 (WhiteOnMove(currentMove)
6726                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6727                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6728             // click in right holdings, for determining promotion piece
6729             ChessSquare p = boards[currentMove][y][x];
6730             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6731             if(p != EmptySquare) {
6732                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6733                 fromX = fromY = -1;
6734                 return;
6735             }
6736         }
6737         DrawPosition(FALSE, boards[currentMove]);
6738         return;
6739     }
6740
6741     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6742     if(clickType == Press
6743             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6744               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6745               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6746         return;
6747
6748     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6749         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6750
6751     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6752         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6753                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6754         defaultPromoChoice = DefaultPromoChoice(side);
6755     }
6756
6757     autoQueen = appData.alwaysPromoteToQueen;
6758
6759     if (fromX == -1) {
6760       int originalY = y;
6761       gatingPiece = EmptySquare;
6762       if (clickType != Press) {
6763         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6764             DragPieceEnd(xPix, yPix); dragging = 0;
6765             DrawPosition(FALSE, NULL);
6766         }
6767         return;
6768       }
6769       fromX = x; fromY = y;
6770       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6771          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6772          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6773             /* First square */
6774             if (OKToStartUserMove(fromX, fromY)) {
6775                 second = 0;
6776                 MarkTargetSquares(0);
6777                 DragPieceBegin(xPix, yPix); dragging = 1;
6778                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6779                     promoSweep = defaultPromoChoice;
6780                     selectFlag = 0; lastX = xPix; lastY = yPix;
6781                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6782                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6783                 }
6784                 if (appData.highlightDragging) {
6785                     SetHighlights(fromX, fromY, -1, -1);
6786                 }
6787             } else fromX = fromY = -1;
6788             return;
6789         }
6790     }
6791
6792     /* fromX != -1 */
6793     if (clickType == Press && gameMode != EditPosition) {
6794         ChessSquare fromP;
6795         ChessSquare toP;
6796         int frc;
6797
6798         // ignore off-board to clicks
6799         if(y < 0 || x < 0) return;
6800
6801         /* Check if clicking again on the same color piece */
6802         fromP = boards[currentMove][fromY][fromX];
6803         toP = boards[currentMove][y][x];
6804         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6805         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6806              WhitePawn <= toP && toP <= WhiteKing &&
6807              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6808              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6809             (BlackPawn <= fromP && fromP <= BlackKing &&
6810              BlackPawn <= toP && toP <= BlackKing &&
6811              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6812              !(fromP == BlackKing && toP == BlackRook && frc))) {
6813             /* Clicked again on same color piece -- changed his mind */
6814             second = (x == fromX && y == fromY);
6815             promoDefaultAltered = FALSE;
6816            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6817             if (appData.highlightDragging) {
6818                 SetHighlights(x, y, -1, -1);
6819             } else {
6820                 ClearHighlights();
6821             }
6822             if (OKToStartUserMove(x, y)) {
6823                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6824                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6825                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6826                  gatingPiece = boards[currentMove][fromY][fromX];
6827                 else gatingPiece = EmptySquare;
6828                 fromX = x;
6829                 fromY = y; dragging = 1;
6830                 MarkTargetSquares(0);
6831                 DragPieceBegin(xPix, yPix);
6832                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6833                     promoSweep = defaultPromoChoice;
6834                     selectFlag = 0; lastX = xPix; lastY = yPix;
6835                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6836                 }
6837             }
6838            }
6839            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6840            second = FALSE; 
6841         }
6842         // ignore clicks on holdings
6843         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6844     }
6845
6846     if (clickType == Release && x == fromX && y == fromY) {
6847         DragPieceEnd(xPix, yPix); dragging = 0;
6848         if(clearFlag) {
6849             // a deferred attempt to click-click move an empty square on top of a piece
6850             boards[currentMove][y][x] = EmptySquare;
6851             ClearHighlights();
6852             DrawPosition(FALSE, boards[currentMove]);
6853             fromX = fromY = -1; clearFlag = 0;
6854             return;
6855         }
6856         if (appData.animateDragging) {
6857             /* Undo animation damage if any */
6858             DrawPosition(FALSE, NULL);
6859         }
6860         if (second) {
6861             /* Second up/down in same square; just abort move */
6862             second = 0;
6863             fromX = fromY = -1;
6864             gatingPiece = EmptySquare;
6865             ClearHighlights();
6866             gotPremove = 0;
6867             ClearPremoveHighlights();
6868         } else {
6869             /* First upclick in same square; start click-click mode */
6870             SetHighlights(x, y, -1, -1);
6871         }
6872         return;
6873     }
6874
6875     clearFlag = 0;
6876
6877     /* we now have a different from- and (possibly off-board) to-square */
6878     /* Completed move */
6879     toX = x;
6880     toY = y;
6881     saveAnimate = appData.animate;
6882     if (clickType == Press) {
6883         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6884             // must be Edit Position mode with empty-square selected
6885             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6886             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6887             return;
6888         }
6889         /* Finish clickclick move */
6890         if (appData.animate || appData.highlightLastMove) {
6891             SetHighlights(fromX, fromY, toX, toY);
6892         } else {
6893             ClearHighlights();
6894         }
6895     } else {
6896         /* Finish drag move */
6897         if (appData.highlightLastMove) {
6898             SetHighlights(fromX, fromY, toX, toY);
6899         } else {
6900             ClearHighlights();
6901         }
6902         DragPieceEnd(xPix, yPix); dragging = 0;
6903         /* Don't animate move and drag both */
6904         appData.animate = FALSE;
6905     }
6906
6907     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6908     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6909         ChessSquare piece = boards[currentMove][fromY][fromX];
6910         if(gameMode == EditPosition && piece != EmptySquare &&
6911            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6912             int n;
6913
6914             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6915                 n = PieceToNumber(piece - (int)BlackPawn);
6916                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6917                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6918                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6919             } else
6920             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6921                 n = PieceToNumber(piece);
6922                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6923                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6924                 boards[currentMove][n][BOARD_WIDTH-2]++;
6925             }
6926             boards[currentMove][fromY][fromX] = EmptySquare;
6927         }
6928         ClearHighlights();
6929         fromX = fromY = -1;
6930         DrawPosition(TRUE, boards[currentMove]);
6931         return;
6932     }
6933
6934     // off-board moves should not be highlighted
6935     if(x < 0 || y < 0) ClearHighlights();
6936
6937     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6938
6939     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6940         SetHighlights(fromX, fromY, toX, toY);
6941         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6942             // [HGM] super: promotion to captured piece selected from holdings
6943             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6944             promotionChoice = TRUE;
6945             // kludge follows to temporarily execute move on display, without promoting yet
6946             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6947             boards[currentMove][toY][toX] = p;
6948             DrawPosition(FALSE, boards[currentMove]);
6949             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6950             boards[currentMove][toY][toX] = q;
6951             DisplayMessage("Click in holdings to choose piece", "");
6952             return;
6953         }
6954         PromotionPopUp();
6955     } else {
6956         int oldMove = currentMove;
6957         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6958         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6959         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6960         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6961            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6962             DrawPosition(TRUE, boards[currentMove]);
6963         fromX = fromY = -1;
6964     }
6965     appData.animate = saveAnimate;
6966     if (appData.animate || appData.animateDragging) {
6967         /* Undo animation damage if needed */
6968         DrawPosition(FALSE, NULL);
6969     }
6970 }
6971
6972 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6973 {   // front-end-free part taken out of PieceMenuPopup
6974     int whichMenu; int xSqr, ySqr;
6975
6976     if(seekGraphUp) { // [HGM] seekgraph
6977         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6978         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6979         return -2;
6980     }
6981
6982     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6983          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6984         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6985         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6986         if(action == Press)   {
6987             originalFlip = flipView;
6988             flipView = !flipView; // temporarily flip board to see game from partners perspective
6989             DrawPosition(TRUE, partnerBoard);
6990             DisplayMessage(partnerStatus, "");
6991             partnerUp = TRUE;
6992         } else if(action == Release) {
6993             flipView = originalFlip;
6994             DrawPosition(TRUE, boards[currentMove]);
6995             partnerUp = FALSE;
6996         }
6997         return -2;
6998     }
6999
7000     xSqr = EventToSquare(x, BOARD_WIDTH);
7001     ySqr = EventToSquare(y, BOARD_HEIGHT);
7002     if (action == Release) {
7003         if(pieceSweep != EmptySquare) {
7004             EditPositionMenuEvent(pieceSweep, toX, toY);
7005             pieceSweep = EmptySquare;
7006         } else UnLoadPV(); // [HGM] pv
7007     }
7008     if (action != Press) return -2; // return code to be ignored
7009     switch (gameMode) {
7010       case IcsExamining:
7011         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7012       case EditPosition:
7013         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7014         if (xSqr < 0 || ySqr < 0) return -1;
7015         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7016         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7017         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7018         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7019         NextPiece(0);
7020         return -2;\r
7021       case IcsObserving:
7022         if(!appData.icsEngineAnalyze) return -1;
7023       case IcsPlayingWhite:
7024       case IcsPlayingBlack:
7025         if(!appData.zippyPlay) goto noZip;
7026       case AnalyzeMode:
7027       case AnalyzeFile:
7028       case MachinePlaysWhite:
7029       case MachinePlaysBlack:
7030       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7031         if (!appData.dropMenu) {
7032           LoadPV(x, y);
7033           return 2; // flag front-end to grab mouse events
7034         }
7035         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7036            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7037       case EditGame:
7038       noZip:
7039         if (xSqr < 0 || ySqr < 0) return -1;
7040         if (!appData.dropMenu || appData.testLegality &&
7041             gameInfo.variant != VariantBughouse &&
7042             gameInfo.variant != VariantCrazyhouse) return -1;
7043         whichMenu = 1; // drop menu
7044         break;
7045       default:
7046         return -1;
7047     }
7048
7049     if (((*fromX = xSqr) < 0) ||
7050         ((*fromY = ySqr) < 0)) {
7051         *fromX = *fromY = -1;
7052         return -1;
7053     }
7054     if (flipView)
7055       *fromX = BOARD_WIDTH - 1 - *fromX;
7056     else
7057       *fromY = BOARD_HEIGHT - 1 - *fromY;
7058
7059     return whichMenu;
7060 }
7061
7062 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7063 {
7064 //    char * hint = lastHint;
7065     FrontEndProgramStats stats;
7066
7067     stats.which = cps == &first ? 0 : 1;
7068     stats.depth = cpstats->depth;
7069     stats.nodes = cpstats->nodes;
7070     stats.score = cpstats->score;
7071     stats.time = cpstats->time;
7072     stats.pv = cpstats->movelist;
7073     stats.hint = lastHint;
7074     stats.an_move_index = 0;
7075     stats.an_move_count = 0;
7076
7077     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7078         stats.hint = cpstats->move_name;
7079         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7080         stats.an_move_count = cpstats->nr_moves;
7081     }
7082
7083     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
7084
7085     SetProgramStats( &stats );
7086 }
7087
7088 #define MAXPLAYERS 500
7089
7090 char *
7091 TourneyStandings(int display)
7092 {
7093     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7094     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7095     char result, *p, *names[MAXPLAYERS];
7096
7097     names[0] = p = strdup(appData.participants);
7098     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7099
7100     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7101
7102     while(result = appData.results[nr]) {
7103         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7104         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7105         wScore = bScore = 0;
7106         switch(result) {
7107           case '+': wScore = 2; break;
7108           case '-': bScore = 2; break;
7109           case '=': wScore = bScore = 1; break;
7110           case ' ':
7111           case '*': return strdup("busy"); // tourney not finished
7112         }
7113         score[w] += wScore;
7114         score[b] += bScore;
7115         games[w]++;
7116         games[b]++;
7117         nr++;
7118     }
7119     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7120     for(w=0; w<nPlayers; w++) {
7121         bScore = -1;
7122         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7123         ranking[w] = b; points[w] = bScore; score[b] = -2;
7124     }
7125     p = malloc(nPlayers*34+1);
7126     for(w=0; w<nPlayers && w<display; w++)
7127         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7128     free(names[0]);
7129     return p;
7130 }
7131
7132 void
7133 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7134 {       // count all piece types
7135         int p, f, r;
7136         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7137         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7138         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7139                 p = board[r][f];
7140                 pCnt[p]++;
7141                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7142                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7143                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7144                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7145                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7146                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7147         }
7148 }
7149
7150 int
7151 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7152 {
7153         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7154         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7155
7156         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7157         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7158         if(myPawns == 2 && nMine == 3) // KPP
7159             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7160         if(myPawns == 1 && nMine == 2) // KP
7161             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7162         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7163             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7164         if(myPawns) return FALSE;
7165         if(pCnt[WhiteRook+side])
7166             return pCnt[BlackRook-side] ||
7167                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7168                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7169                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7170         if(pCnt[WhiteCannon+side]) {
7171             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7172             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7173         }
7174         if(pCnt[WhiteKnight+side])
7175             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7176         return FALSE;
7177 }
7178
7179 int
7180 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7181 {
7182         VariantClass v = gameInfo.variant;
7183
7184         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7185         if(v == VariantShatranj) return TRUE; // always winnable through baring
7186         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7187         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7188
7189         if(v == VariantXiangqi) {
7190                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7191
7192                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7193                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7194                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7195                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7196                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7197                 if(stale) // we have at least one last-rank P plus perhaps C
7198                     return majors // KPKX
7199                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7200                 else // KCA*E*
7201                     return pCnt[WhiteFerz+side] // KCAK
7202                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7203                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7204                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7205
7206         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7207                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7208
7209                 if(nMine == 1) return FALSE; // bare King
7210                 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
7211                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7212                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7213                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7214                 if(pCnt[WhiteKnight+side])
7215                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7216                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7217                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7218                 if(nBishops)
7219                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7220                 if(pCnt[WhiteAlfil+side])
7221                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7222                 if(pCnt[WhiteWazir+side])
7223                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7224         }
7225
7226         return TRUE;
7227 }
7228
7229 int
7230 Adjudicate(ChessProgramState *cps)
7231 {       // [HGM] some adjudications useful with buggy engines
7232         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7233         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7234         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7235         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7236         int k, count = 0; static int bare = 1;
7237         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7238         Boolean canAdjudicate = !appData.icsActive;
7239
7240         // most tests only when we understand the game, i.e. legality-checking on
7241             if( appData.testLegality )
7242             {   /* [HGM] Some more adjudications for obstinate engines */
7243                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7244                 static int moveCount = 6;
7245                 ChessMove result;
7246                 char *reason = NULL;
7247
7248                 /* Count what is on board. */
7249                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7250
7251                 /* Some material-based adjudications that have to be made before stalemate test */
7252                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7253                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7254                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7255                      if(canAdjudicate && appData.checkMates) {
7256                          if(engineOpponent)
7257                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7258                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7259                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7260                          return 1;
7261                      }
7262                 }
7263
7264                 /* Bare King in Shatranj (loses) or Losers (wins) */
7265                 if( nrW == 1 || nrB == 1) {
7266                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7267                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7268                      if(canAdjudicate && appData.checkMates) {
7269                          if(engineOpponent)
7270                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7271                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7272                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7273                          return 1;
7274                      }
7275                   } else
7276                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7277                   {    /* bare King */
7278                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7279                         if(canAdjudicate && appData.checkMates) {
7280                             /* but only adjudicate if adjudication enabled */
7281                             if(engineOpponent)
7282                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7283                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7284                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7285                             return 1;
7286                         }
7287                   }
7288                 } else bare = 1;
7289
7290
7291             // don't wait for engine to announce game end if we can judge ourselves
7292             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7293               case MT_CHECK:
7294                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7295                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7296                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7297                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7298                             checkCnt++;
7299                         if(checkCnt >= 2) {
7300                             reason = "Xboard adjudication: 3rd check";
7301                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7302                             break;
7303                         }
7304                     }
7305                 }
7306               case MT_NONE:
7307               default:
7308                 break;
7309               case MT_STALEMATE:
7310               case MT_STAINMATE:
7311                 reason = "Xboard adjudication: Stalemate";
7312                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7313                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7314                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7315                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7316                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7317                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7318                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7319                                                                         EP_CHECKMATE : EP_WINS);
7320                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7321                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7322                 }
7323                 break;
7324               case MT_CHECKMATE:
7325                 reason = "Xboard adjudication: Checkmate";
7326                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7327                 break;
7328             }
7329
7330                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7331                     case EP_STALEMATE:
7332                         result = GameIsDrawn; break;
7333                     case EP_CHECKMATE:
7334                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7335                     case EP_WINS:
7336                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7337                     default:
7338                         result = EndOfFile;
7339                 }
7340                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7341                     if(engineOpponent)
7342                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7343                     GameEnds( result, reason, GE_XBOARD );
7344                     return 1;
7345                 }
7346
7347                 /* Next absolutely insufficient mating material. */
7348                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7349                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7350                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7351
7352                      /* always flag draws, for judging claims */
7353                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7354
7355                      if(canAdjudicate && appData.materialDraws) {
7356                          /* but only adjudicate them if adjudication enabled */
7357                          if(engineOpponent) {
7358                            SendToProgram("force\n", engineOpponent); // suppress reply
7359                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7360                          }
7361                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7362                          return 1;
7363                      }
7364                 }
7365
7366                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7367                 if(gameInfo.variant == VariantXiangqi ?
7368                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7369                  : nrW + nrB == 4 &&
7370                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7371                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7372                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7373                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7374                    ) ) {
7375                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7376                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7377                           if(engineOpponent) {
7378                             SendToProgram("force\n", engineOpponent); // suppress reply
7379                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7380                           }
7381                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7382                           return 1;
7383                      }
7384                 } else moveCount = 6;
7385             }
7386         if (appData.debugMode) { int i;
7387             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7388                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7389                     appData.drawRepeats);
7390             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7391               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7392
7393         }
7394
7395         // Repetition draws and 50-move rule can be applied independently of legality testing
7396
7397                 /* Check for rep-draws */
7398                 count = 0;
7399                 for(k = forwardMostMove-2;
7400                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7401                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7402                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7403                     k-=2)
7404                 {   int rights=0;
7405                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7406                         /* compare castling rights */
7407                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7408                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7409                                 rights++; /* King lost rights, while rook still had them */
7410                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7411                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7412                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7413                                    rights++; /* but at least one rook lost them */
7414                         }
7415                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7416                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7417                                 rights++;
7418                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7419                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7420                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7421                                    rights++;
7422                         }
7423                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7424                             && appData.drawRepeats > 1) {
7425                              /* adjudicate after user-specified nr of repeats */
7426                              int result = GameIsDrawn;
7427                              char *details = "XBoard adjudication: repetition draw";
7428                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7429                                 // [HGM] xiangqi: check for forbidden perpetuals
7430                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7431                                 for(m=forwardMostMove; m>k; m-=2) {
7432                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7433                                         ourPerpetual = 0; // the current mover did not always check
7434                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7435                                         hisPerpetual = 0; // the opponent did not always check
7436                                 }
7437                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7438                                                                         ourPerpetual, hisPerpetual);
7439                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7440                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7441                                     details = "Xboard adjudication: perpetual checking";
7442                                 } else
7443                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7444                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7445                                 } else
7446                                 // Now check for perpetual chases
7447                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7448                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7449                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7450                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7451                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7452                                         details = "Xboard adjudication: perpetual chasing";
7453                                     } else
7454                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7455                                         break; // Abort repetition-checking loop.
7456                                 }
7457                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7458                              }
7459                              if(engineOpponent) {
7460                                SendToProgram("force\n", engineOpponent); // suppress reply
7461                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7462                              }
7463                              GameEnds( result, details, GE_XBOARD );
7464                              return 1;
7465                         }
7466                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7467                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7468                     }
7469                 }
7470
7471                 /* Now we test for 50-move draws. Determine ply count */
7472                 count = forwardMostMove;
7473                 /* look for last irreversble move */
7474                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7475                     count--;
7476                 /* if we hit starting position, add initial plies */
7477                 if( count == backwardMostMove )
7478                     count -= initialRulePlies;
7479                 count = forwardMostMove - count;
7480                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7481                         // adjust reversible move counter for checks in Xiangqi
7482                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7483                         if(i < backwardMostMove) i = backwardMostMove;
7484                         while(i <= forwardMostMove) {
7485                                 lastCheck = inCheck; // check evasion does not count
7486                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7487                                 if(inCheck || lastCheck) count--; // check does not count
7488                                 i++;
7489                         }
7490                 }
7491                 if( count >= 100)
7492                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7493                          /* this is used to judge if draw claims are legal */
7494                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7495                          if(engineOpponent) {
7496                            SendToProgram("force\n", engineOpponent); // suppress reply
7497                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7498                          }
7499                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7500                          return 1;
7501                 }
7502
7503                 /* if draw offer is pending, treat it as a draw claim
7504                  * when draw condition present, to allow engines a way to
7505                  * claim draws before making their move to avoid a race
7506                  * condition occurring after their move
7507                  */
7508                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7509                          char *p = NULL;
7510                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7511                              p = "Draw claim: 50-move rule";
7512                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7513                              p = "Draw claim: 3-fold repetition";
7514                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7515                              p = "Draw claim: insufficient mating material";
7516                          if( p != NULL && canAdjudicate) {
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, p, GE_XBOARD );
7522                              return 1;
7523                          }
7524                 }
7525
7526                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7527                     if(engineOpponent) {
7528                       SendToProgram("force\n", engineOpponent); // suppress reply
7529                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7530                     }
7531                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7532                     return 1;
7533                 }
7534         return 0;
7535 }
7536
7537 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7538 {   // [HGM] book: this routine intercepts moves to simulate book replies
7539     char *bookHit = NULL;
7540
7541     //first determine if the incoming move brings opponent into his book
7542     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7543         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7544     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7545     if(bookHit != NULL && !cps->bookSuspend) {
7546         // make sure opponent is not going to reply after receiving move to book position
7547         SendToProgram("force\n", cps);
7548         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7549     }
7550     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7551     // now arrange restart after book miss
7552     if(bookHit) {
7553         // after a book hit we never send 'go', and the code after the call to this routine
7554         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7555         char buf[MSG_SIZ];
7556         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7557         SendToProgram(buf, cps);
7558         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7559     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7560         SendToProgram("go\n", cps);
7561         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7562     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7563         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7564             SendToProgram("go\n", cps);
7565         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7566     }
7567     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7568 }
7569
7570 char *savedMessage;
7571 ChessProgramState *savedState;
7572 void DeferredBookMove(void)
7573 {
7574         if(savedState->lastPing != savedState->lastPong)
7575                     ScheduleDelayedEvent(DeferredBookMove, 10);
7576         else
7577         HandleMachineMove(savedMessage, savedState);
7578 }
7579
7580 void
7581 HandleMachineMove(message, cps)
7582      char *message;
7583      ChessProgramState *cps;
7584 {
7585     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7586     char realname[MSG_SIZ];
7587     int fromX, fromY, toX, toY;
7588     ChessMove moveType;
7589     char promoChar;
7590     char *p;
7591     int machineWhite;
7592     char *bookHit;
7593
7594     cps->userError = 0;
7595
7596 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7597     /*
7598      * Kludge to ignore BEL characters
7599      */
7600     while (*message == '\007') message++;
7601
7602     /*
7603      * [HGM] engine debug message: ignore lines starting with '#' character
7604      */
7605     if(cps->debug && *message == '#') return;
7606
7607     /*
7608      * Look for book output
7609      */
7610     if (cps == &first && bookRequested) {
7611         if (message[0] == '\t' || message[0] == ' ') {
7612             /* Part of the book output is here; append it */
7613             strcat(bookOutput, message);
7614             strcat(bookOutput, "  \n");
7615             return;
7616         } else if (bookOutput[0] != NULLCHAR) {
7617             /* All of book output has arrived; display it */
7618             char *p = bookOutput;
7619             while (*p != NULLCHAR) {
7620                 if (*p == '\t') *p = ' ';
7621                 p++;
7622             }
7623             DisplayInformation(bookOutput);
7624             bookRequested = FALSE;
7625             /* Fall through to parse the current output */
7626         }
7627     }
7628
7629     /*
7630      * Look for machine move.
7631      */
7632     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7633         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7634     {
7635         /* This method is only useful on engines that support ping */
7636         if (cps->lastPing != cps->lastPong) {
7637           if (gameMode == BeginningOfGame) {
7638             /* Extra move from before last new; ignore */
7639             if (appData.debugMode) {
7640                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7641             }
7642           } else {
7643             if (appData.debugMode) {
7644                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7645                         cps->which, gameMode);
7646             }
7647
7648             SendToProgram("undo\n", cps);
7649           }
7650           return;
7651         }
7652
7653         switch (gameMode) {
7654           case BeginningOfGame:
7655             /* Extra move from before last reset; ignore */
7656             if (appData.debugMode) {
7657                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7658             }
7659             return;
7660
7661           case EndOfGame:
7662           case IcsIdle:
7663           default:
7664             /* Extra move after we tried to stop.  The mode test is
7665                not a reliable way of detecting this problem, but it's
7666                the best we can do on engines that don't support ping.
7667             */
7668             if (appData.debugMode) {
7669                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7670                         cps->which, gameMode);
7671             }
7672             SendToProgram("undo\n", cps);
7673             return;
7674
7675           case MachinePlaysWhite:
7676           case IcsPlayingWhite:
7677             machineWhite = TRUE;
7678             break;
7679
7680           case MachinePlaysBlack:
7681           case IcsPlayingBlack:
7682             machineWhite = FALSE;
7683             break;
7684
7685           case TwoMachinesPlay:
7686             machineWhite = (cps->twoMachinesColor[0] == 'w');
7687             break;
7688         }
7689         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7690             if (appData.debugMode) {
7691                 fprintf(debugFP,
7692                         "Ignoring move out of turn by %s, gameMode %d"
7693                         ", forwardMost %d\n",
7694                         cps->which, gameMode, forwardMostMove);
7695             }
7696             return;
7697         }
7698
7699     if (appData.debugMode) { int f = forwardMostMove;
7700         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7701                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7702                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7703     }
7704         if(cps->alphaRank) AlphaRank(machineMove, 4);
7705         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7706                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7707             /* Machine move could not be parsed; ignore it. */
7708           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7709                     machineMove, _(cps->which));
7710             DisplayError(buf1, 0);
7711             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7712                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7713             if (gameMode == TwoMachinesPlay) {
7714               GameEnds(machineWhite ? BlackWins : WhiteWins,
7715                        buf1, GE_XBOARD);
7716             }
7717             return;
7718         }
7719
7720         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7721         /* So we have to redo legality test with true e.p. status here,  */
7722         /* to make sure an illegal e.p. capture does not slip through,   */
7723         /* to cause a forfeit on a justified illegal-move complaint      */
7724         /* of the opponent.                                              */
7725         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7726            ChessMove moveType;
7727            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7728                              fromY, fromX, toY, toX, promoChar);
7729             if (appData.debugMode) {
7730                 int i;
7731                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7732                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7733                 fprintf(debugFP, "castling rights\n");
7734             }
7735             if(moveType == IllegalMove) {
7736               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7737                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7738                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7739                            buf1, GE_XBOARD);
7740                 return;
7741            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7742            /* [HGM] Kludge to handle engines that send FRC-style castling
7743               when they shouldn't (like TSCP-Gothic) */
7744            switch(moveType) {
7745              case WhiteASideCastleFR:
7746              case BlackASideCastleFR:
7747                toX+=2;
7748                currentMoveString[2]++;
7749                break;
7750              case WhiteHSideCastleFR:
7751              case BlackHSideCastleFR:
7752                toX--;
7753                currentMoveString[2]--;
7754                break;
7755              default: ; // nothing to do, but suppresses warning of pedantic compilers
7756            }
7757         }
7758         hintRequested = FALSE;
7759         lastHint[0] = NULLCHAR;
7760         bookRequested = FALSE;
7761         /* Program may be pondering now */
7762         cps->maybeThinking = TRUE;
7763         if (cps->sendTime == 2) cps->sendTime = 1;
7764         if (cps->offeredDraw) cps->offeredDraw--;
7765
7766         /* [AS] Save move info*/
7767         pvInfoList[ forwardMostMove ].score = programStats.score;
7768         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7769         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7770
7771         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7772
7773         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7774         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7775             int count = 0;
7776
7777             while( count < adjudicateLossPlies ) {
7778                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7779
7780                 if( count & 1 ) {
7781                     score = -score; /* Flip score for winning side */
7782                 }
7783
7784                 if( score > adjudicateLossThreshold ) {
7785                     break;
7786                 }
7787
7788                 count++;
7789             }
7790
7791             if( count >= adjudicateLossPlies ) {
7792                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7793
7794                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7795                     "Xboard adjudication",
7796                     GE_XBOARD );
7797
7798                 return;
7799             }
7800         }
7801
7802         if(Adjudicate(cps)) {
7803             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7804             return; // [HGM] adjudicate: for all automatic game ends
7805         }
7806
7807 #if ZIPPY
7808         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7809             first.initDone) {
7810           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7811                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7812                 SendToICS("draw ");
7813                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7814           }
7815           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7816           ics_user_moved = 1;
7817           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7818                 char buf[3*MSG_SIZ];
7819
7820                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7821                         programStats.score / 100.,
7822                         programStats.depth,
7823                         programStats.time / 100.,
7824                         (unsigned int)programStats.nodes,
7825                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7826                         programStats.movelist);
7827                 SendToICS(buf);
7828 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7829           }
7830         }
7831 #endif
7832
7833         /* [AS] Clear stats for next move */
7834         ClearProgramStats();
7835         thinkOutput[0] = NULLCHAR;
7836         hiddenThinkOutputState = 0;
7837
7838         bookHit = NULL;
7839         if (gameMode == TwoMachinesPlay) {
7840             /* [HGM] relaying draw offers moved to after reception of move */
7841             /* and interpreting offer as claim if it brings draw condition */
7842             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7843                 SendToProgram("draw\n", cps->other);
7844             }
7845             if (cps->other->sendTime) {
7846                 SendTimeRemaining(cps->other,
7847                                   cps->other->twoMachinesColor[0] == 'w');
7848             }
7849             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7850             if (firstMove && !bookHit) {
7851                 firstMove = FALSE;
7852                 if (cps->other->useColors) {
7853                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7854                 }
7855                 SendToProgram("go\n", cps->other);
7856             }
7857             cps->other->maybeThinking = TRUE;
7858         }
7859
7860         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7861
7862         if (!pausing && appData.ringBellAfterMoves) {
7863             RingBell();
7864         }
7865
7866         /*
7867          * Reenable menu items that were disabled while
7868          * machine was thinking
7869          */
7870         if (gameMode != TwoMachinesPlay)
7871             SetUserThinkingEnables();
7872
7873         // [HGM] book: after book hit opponent has received move and is now in force mode
7874         // force the book reply into it, and then fake that it outputted this move by jumping
7875         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7876         if(bookHit) {
7877                 static char bookMove[MSG_SIZ]; // a bit generous?
7878
7879                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7880                 strcat(bookMove, bookHit);
7881                 message = bookMove;
7882                 cps = cps->other;
7883                 programStats.nodes = programStats.depth = programStats.time =
7884                 programStats.score = programStats.got_only_move = 0;
7885                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7886
7887                 if(cps->lastPing != cps->lastPong) {
7888                     savedMessage = message; // args for deferred call
7889                     savedState = cps;
7890                     ScheduleDelayedEvent(DeferredBookMove, 10);
7891                     return;
7892                 }
7893                 goto FakeBookMove;
7894         }
7895
7896         return;
7897     }
7898
7899     /* Set special modes for chess engines.  Later something general
7900      *  could be added here; for now there is just one kludge feature,
7901      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7902      *  when "xboard" is given as an interactive command.
7903      */
7904     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7905         cps->useSigint = FALSE;
7906         cps->useSigterm = FALSE;
7907     }
7908     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7909       ParseFeatures(message+8, cps);
7910       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7911     }
7912
7913     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7914       int dummy, s=6; char buf[MSG_SIZ];
7915       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7916       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7917       ParseFEN(boards[0], &dummy, message+s);
7918       DrawPosition(TRUE, boards[0]);
7919       startedFromSetupPosition = TRUE;
7920       return;
7921     }
7922     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7923      * want this, I was asked to put it in, and obliged.
7924      */
7925     if (!strncmp(message, "setboard ", 9)) {
7926         Board initial_position;
7927
7928         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7929
7930         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7931             DisplayError(_("Bad FEN received from engine"), 0);
7932             return ;
7933         } else {
7934            Reset(TRUE, FALSE);
7935            CopyBoard(boards[0], initial_position);
7936            initialRulePlies = FENrulePlies;
7937            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7938            else gameMode = MachinePlaysBlack;
7939            DrawPosition(FALSE, boards[currentMove]);
7940         }
7941         return;
7942     }
7943
7944     /*
7945      * Look for communication commands
7946      */
7947     if (!strncmp(message, "telluser ", 9)) {
7948         if(message[9] == '\\' && message[10] == '\\')
7949             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7950         DisplayNote(message + 9);
7951         return;
7952     }
7953     if (!strncmp(message, "tellusererror ", 14)) {
7954         cps->userError = 1;
7955         if(message[14] == '\\' && message[15] == '\\')
7956             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7957         DisplayError(message + 14, 0);
7958         return;
7959     }
7960     if (!strncmp(message, "tellopponent ", 13)) {
7961       if (appData.icsActive) {
7962         if (loggedOn) {
7963           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7964           SendToICS(buf1);
7965         }
7966       } else {
7967         DisplayNote(message + 13);
7968       }
7969       return;
7970     }
7971     if (!strncmp(message, "tellothers ", 11)) {
7972       if (appData.icsActive) {
7973         if (loggedOn) {
7974           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7975           SendToICS(buf1);
7976         }
7977       }
7978       return;
7979     }
7980     if (!strncmp(message, "tellall ", 8)) {
7981       if (appData.icsActive) {
7982         if (loggedOn) {
7983           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7984           SendToICS(buf1);
7985         }
7986       } else {
7987         DisplayNote(message + 8);
7988       }
7989       return;
7990     }
7991     if (strncmp(message, "warning", 7) == 0) {
7992         /* Undocumented feature, use tellusererror in new code */
7993         DisplayError(message, 0);
7994         return;
7995     }
7996     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7997         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7998         strcat(realname, " query");
7999         AskQuestion(realname, buf2, buf1, cps->pr);
8000         return;
8001     }
8002     /* Commands from the engine directly to ICS.  We don't allow these to be
8003      *  sent until we are logged on. Crafty kibitzes have been known to
8004      *  interfere with the login process.
8005      */
8006     if (loggedOn) {
8007         if (!strncmp(message, "tellics ", 8)) {
8008             SendToICS(message + 8);
8009             SendToICS("\n");
8010             return;
8011         }
8012         if (!strncmp(message, "tellicsnoalias ", 15)) {
8013             SendToICS(ics_prefix);
8014             SendToICS(message + 15);
8015             SendToICS("\n");
8016             return;
8017         }
8018         /* The following are for backward compatibility only */
8019         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8020             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8021             SendToICS(ics_prefix);
8022             SendToICS(message);
8023             SendToICS("\n");
8024             return;
8025         }
8026     }
8027     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8028         return;
8029     }
8030     /*
8031      * If the move is illegal, cancel it and redraw the board.
8032      * Also deal with other error cases.  Matching is rather loose
8033      * here to accommodate engines written before the spec.
8034      */
8035     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8036         strncmp(message, "Error", 5) == 0) {
8037         if (StrStr(message, "name") ||
8038             StrStr(message, "rating") || StrStr(message, "?") ||
8039             StrStr(message, "result") || StrStr(message, "board") ||
8040             StrStr(message, "bk") || StrStr(message, "computer") ||
8041             StrStr(message, "variant") || StrStr(message, "hint") ||
8042             StrStr(message, "random") || StrStr(message, "depth") ||
8043             StrStr(message, "accepted")) {
8044             return;
8045         }
8046         if (StrStr(message, "protover")) {
8047           /* Program is responding to input, so it's apparently done
8048              initializing, and this error message indicates it is
8049              protocol version 1.  So we don't need to wait any longer
8050              for it to initialize and send feature commands. */
8051           FeatureDone(cps, 1);
8052           cps->protocolVersion = 1;
8053           return;
8054         }
8055         cps->maybeThinking = FALSE;
8056
8057         if (StrStr(message, "draw")) {
8058             /* Program doesn't have "draw" command */
8059             cps->sendDrawOffers = 0;
8060             return;
8061         }
8062         if (cps->sendTime != 1 &&
8063             (StrStr(message, "time") || StrStr(message, "otim"))) {
8064           /* Program apparently doesn't have "time" or "otim" command */
8065           cps->sendTime = 0;
8066           return;
8067         }
8068         if (StrStr(message, "analyze")) {
8069             cps->analysisSupport = FALSE;
8070             cps->analyzing = FALSE;
8071             Reset(FALSE, TRUE);
8072             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8073             DisplayError(buf2, 0);
8074             return;
8075         }
8076         if (StrStr(message, "(no matching move)st")) {
8077           /* Special kludge for GNU Chess 4 only */
8078           cps->stKludge = TRUE;
8079           SendTimeControl(cps, movesPerSession, timeControl,
8080                           timeIncrement, appData.searchDepth,
8081                           searchTime);
8082           return;
8083         }
8084         if (StrStr(message, "(no matching move)sd")) {
8085           /* Special kludge for GNU Chess 4 only */
8086           cps->sdKludge = TRUE;
8087           SendTimeControl(cps, movesPerSession, timeControl,
8088                           timeIncrement, appData.searchDepth,
8089                           searchTime);
8090           return;
8091         }
8092         if (!StrStr(message, "llegal")) {
8093             return;
8094         }
8095         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8096             gameMode == IcsIdle) return;
8097         if (forwardMostMove <= backwardMostMove) return;
8098         if (pausing) PauseEvent();
8099       if(appData.forceIllegal) {
8100             // [HGM] illegal: machine refused move; force position after move into it
8101           SendToProgram("force\n", cps);
8102           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8103                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8104                 // when black is to move, while there might be nothing on a2 or black
8105                 // might already have the move. So send the board as if white has the move.
8106                 // But first we must change the stm of the engine, as it refused the last move
8107                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8108                 if(WhiteOnMove(forwardMostMove)) {
8109                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8110                     SendBoard(cps, forwardMostMove); // kludgeless board
8111                 } else {
8112                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8113                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8114                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8115                 }
8116           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8117             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8118                  gameMode == TwoMachinesPlay)
8119               SendToProgram("go\n", cps);
8120             return;
8121       } else
8122         if (gameMode == PlayFromGameFile) {
8123             /* Stop reading this game file */
8124             gameMode = EditGame;
8125             ModeHighlight();
8126         }
8127         /* [HGM] illegal-move claim should forfeit game when Xboard */
8128         /* only passes fully legal moves                            */
8129         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8130             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8131                                 "False illegal-move claim", GE_XBOARD );
8132             return; // do not take back move we tested as valid
8133         }
8134         currentMove = forwardMostMove-1;
8135         DisplayMove(currentMove-1); /* before DisplayMoveError */
8136         SwitchClocks(forwardMostMove-1); // [HGM] race
8137         DisplayBothClocks();
8138         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8139                 parseList[currentMove], _(cps->which));
8140         DisplayMoveError(buf1);
8141         DrawPosition(FALSE, boards[currentMove]);
8142         return;
8143     }
8144     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8145         /* Program has a broken "time" command that
8146            outputs a string not ending in newline.
8147            Don't use it. */
8148         cps->sendTime = 0;
8149     }
8150
8151     /*
8152      * If chess program startup fails, exit with an error message.
8153      * Attempts to recover here are futile.
8154      */
8155     if ((StrStr(message, "unknown host") != NULL)
8156         || (StrStr(message, "No remote directory") != NULL)
8157         || (StrStr(message, "not found") != NULL)
8158         || (StrStr(message, "No such file") != NULL)
8159         || (StrStr(message, "can't alloc") != NULL)
8160         || (StrStr(message, "Permission denied") != NULL)) {
8161
8162         cps->maybeThinking = FALSE;
8163         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8164                 _(cps->which), cps->program, cps->host, message);
8165         RemoveInputSource(cps->isr);
8166         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8167             if(cps == &first) appData.noChessProgram = TRUE;
8168             DisplayError(buf1, 0);
8169         }
8170         return;
8171     }
8172
8173     /*
8174      * Look for hint output
8175      */
8176     if (sscanf(message, "Hint: %s", buf1) == 1) {
8177         if (cps == &first && hintRequested) {
8178             hintRequested = FALSE;
8179             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8180                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8181                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8182                                     PosFlags(forwardMostMove),
8183                                     fromY, fromX, toY, toX, promoChar, buf1);
8184                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8185                 DisplayInformation(buf2);
8186             } else {
8187                 /* Hint move could not be parsed!? */
8188               snprintf(buf2, sizeof(buf2),
8189                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8190                         buf1, _(cps->which));
8191                 DisplayError(buf2, 0);
8192             }
8193         } else {
8194           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8195         }
8196         return;
8197     }
8198
8199     /*
8200      * Ignore other messages if game is not in progress
8201      */
8202     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8203         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8204
8205     /*
8206      * look for win, lose, draw, or draw offer
8207      */
8208     if (strncmp(message, "1-0", 3) == 0) {
8209         char *p, *q, *r = "";
8210         p = strchr(message, '{');
8211         if (p) {
8212             q = strchr(p, '}');
8213             if (q) {
8214                 *q = NULLCHAR;
8215                 r = p + 1;
8216             }
8217         }
8218         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8219         return;
8220     } else if (strncmp(message, "0-1", 3) == 0) {
8221         char *p, *q, *r = "";
8222         p = strchr(message, '{');
8223         if (p) {
8224             q = strchr(p, '}');
8225             if (q) {
8226                 *q = NULLCHAR;
8227                 r = p + 1;
8228             }
8229         }
8230         /* Kludge for Arasan 4.1 bug */
8231         if (strcmp(r, "Black resigns") == 0) {
8232             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8233             return;
8234         }
8235         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8236         return;
8237     } else if (strncmp(message, "1/2", 3) == 0) {
8238         char *p, *q, *r = "";
8239         p = strchr(message, '{');
8240         if (p) {
8241             q = strchr(p, '}');
8242             if (q) {
8243                 *q = NULLCHAR;
8244                 r = p + 1;
8245             }
8246         }
8247
8248         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8249         return;
8250
8251     } else if (strncmp(message, "White resign", 12) == 0) {
8252         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8253         return;
8254     } else if (strncmp(message, "Black resign", 12) == 0) {
8255         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8256         return;
8257     } else if (strncmp(message, "White matches", 13) == 0 ||
8258                strncmp(message, "Black matches", 13) == 0   ) {
8259         /* [HGM] ignore GNUShogi noises */
8260         return;
8261     } else if (strncmp(message, "White", 5) == 0 &&
8262                message[5] != '(' &&
8263                StrStr(message, "Black") == NULL) {
8264         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8265         return;
8266     } else if (strncmp(message, "Black", 5) == 0 &&
8267                message[5] != '(') {
8268         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8269         return;
8270     } else if (strcmp(message, "resign") == 0 ||
8271                strcmp(message, "computer resigns") == 0) {
8272         switch (gameMode) {
8273           case MachinePlaysBlack:
8274           case IcsPlayingBlack:
8275             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8276             break;
8277           case MachinePlaysWhite:
8278           case IcsPlayingWhite:
8279             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8280             break;
8281           case TwoMachinesPlay:
8282             if (cps->twoMachinesColor[0] == 'w')
8283               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8284             else
8285               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8286             break;
8287           default:
8288             /* can't happen */
8289             break;
8290         }
8291         return;
8292     } else if (strncmp(message, "opponent mates", 14) == 0) {
8293         switch (gameMode) {
8294           case MachinePlaysBlack:
8295           case IcsPlayingBlack:
8296             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8297             break;
8298           case MachinePlaysWhite:
8299           case IcsPlayingWhite:
8300             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8301             break;
8302           case TwoMachinesPlay:
8303             if (cps->twoMachinesColor[0] == 'w')
8304               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8305             else
8306               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8307             break;
8308           default:
8309             /* can't happen */
8310             break;
8311         }
8312         return;
8313     } else if (strncmp(message, "computer mates", 14) == 0) {
8314         switch (gameMode) {
8315           case MachinePlaysBlack:
8316           case IcsPlayingBlack:
8317             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8318             break;
8319           case MachinePlaysWhite:
8320           case IcsPlayingWhite:
8321             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8322             break;
8323           case TwoMachinesPlay:
8324             if (cps->twoMachinesColor[0] == 'w')
8325               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8326             else
8327               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8328             break;
8329           default:
8330             /* can't happen */
8331             break;
8332         }
8333         return;
8334     } else if (strncmp(message, "checkmate", 9) == 0) {
8335         if (WhiteOnMove(forwardMostMove)) {
8336             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8337         } else {
8338             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8339         }
8340         return;
8341     } else if (strstr(message, "Draw") != NULL ||
8342                strstr(message, "game is a draw") != NULL) {
8343         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8344         return;
8345     } else if (strstr(message, "offer") != NULL &&
8346                strstr(message, "draw") != NULL) {
8347 #if ZIPPY
8348         if (appData.zippyPlay && first.initDone) {
8349             /* Relay offer to ICS */
8350             SendToICS(ics_prefix);
8351             SendToICS("draw\n");
8352         }
8353 #endif
8354         cps->offeredDraw = 2; /* valid until this engine moves twice */
8355         if (gameMode == TwoMachinesPlay) {
8356             if (cps->other->offeredDraw) {
8357                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8358             /* [HGM] in two-machine mode we delay relaying draw offer      */
8359             /* until after we also have move, to see if it is really claim */
8360             }
8361         } else if (gameMode == MachinePlaysWhite ||
8362                    gameMode == MachinePlaysBlack) {
8363           if (userOfferedDraw) {
8364             DisplayInformation(_("Machine accepts your draw offer"));
8365             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8366           } else {
8367             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8368           }
8369         }
8370     }
8371
8372
8373     /*
8374      * Look for thinking output
8375      */
8376     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8377           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8378                                 ) {
8379         int plylev, mvleft, mvtot, curscore, time;
8380         char mvname[MOVE_LEN];
8381         u64 nodes; // [DM]
8382         char plyext;
8383         int ignore = FALSE;
8384         int prefixHint = FALSE;
8385         mvname[0] = NULLCHAR;
8386
8387         switch (gameMode) {
8388           case MachinePlaysBlack:
8389           case IcsPlayingBlack:
8390             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8391             break;
8392           case MachinePlaysWhite:
8393           case IcsPlayingWhite:
8394             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8395             break;
8396           case AnalyzeMode:
8397           case AnalyzeFile:
8398             break;
8399           case IcsObserving: /* [DM] icsEngineAnalyze */
8400             if (!appData.icsEngineAnalyze) ignore = TRUE;
8401             break;
8402           case TwoMachinesPlay:
8403             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8404                 ignore = TRUE;
8405             }
8406             break;
8407           default:
8408             ignore = TRUE;
8409             break;
8410         }
8411
8412         if (!ignore) {
8413             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8414             buf1[0] = NULLCHAR;
8415             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8416                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8417
8418                 if (plyext != ' ' && plyext != '\t') {
8419                     time *= 100;
8420                 }
8421
8422                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8423                 if( cps->scoreIsAbsolute &&
8424                     ( gameMode == MachinePlaysBlack ||
8425                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8426                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8427                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8428                      !WhiteOnMove(currentMove)
8429                     ) )
8430                 {
8431                     curscore = -curscore;
8432                 }
8433
8434
8435                 tempStats.depth = plylev;
8436                 tempStats.nodes = nodes;
8437                 tempStats.time = time;
8438                 tempStats.score = curscore;
8439                 tempStats.got_only_move = 0;
8440
8441                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8442                         int ticklen;
8443
8444                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8445                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8446                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8447                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8448                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8449                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8450                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8451                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8452                 }
8453
8454                 /* Buffer overflow protection */
8455                 if (buf1[0] != NULLCHAR) {
8456                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8457                         && appData.debugMode) {
8458                         fprintf(debugFP,
8459                                 "PV is too long; using the first %u bytes.\n",
8460                                 (unsigned) sizeof(tempStats.movelist) - 1);
8461                     }
8462
8463                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8464                 } else {
8465                     sprintf(tempStats.movelist, " no PV\n");
8466                 }
8467
8468                 if (tempStats.seen_stat) {
8469                     tempStats.ok_to_send = 1;
8470                 }
8471
8472                 if (strchr(tempStats.movelist, '(') != NULL) {
8473                     tempStats.line_is_book = 1;
8474                     tempStats.nr_moves = 0;
8475                     tempStats.moves_left = 0;
8476                 } else {
8477                     tempStats.line_is_book = 0;
8478                 }
8479
8480                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8481                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8482
8483                 SendProgramStatsToFrontend( cps, &tempStats );
8484
8485                 /*
8486                     [AS] Protect the thinkOutput buffer from overflow... this
8487                     is only useful if buf1 hasn't overflowed first!
8488                 */
8489                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8490                          plylev,
8491                          (gameMode == TwoMachinesPlay ?
8492                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8493                          ((double) curscore) / 100.0,
8494                          prefixHint ? lastHint : "",
8495                          prefixHint ? " " : "" );
8496
8497                 if( buf1[0] != NULLCHAR ) {
8498                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8499
8500                     if( strlen(buf1) > max_len ) {
8501                         if( appData.debugMode) {
8502                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8503                         }
8504                         buf1[max_len+1] = '\0';
8505                     }
8506
8507                     strcat( thinkOutput, buf1 );
8508                 }
8509
8510                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8511                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8512                     DisplayMove(currentMove - 1);
8513                 }
8514                 return;
8515
8516             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8517                 /* crafty (9.25+) says "(only move) <move>"
8518                  * if there is only 1 legal move
8519                  */
8520                 sscanf(p, "(only move) %s", buf1);
8521                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8522                 sprintf(programStats.movelist, "%s (only move)", buf1);
8523                 programStats.depth = 1;
8524                 programStats.nr_moves = 1;
8525                 programStats.moves_left = 1;
8526                 programStats.nodes = 1;
8527                 programStats.time = 1;
8528                 programStats.got_only_move = 1;
8529
8530                 /* Not really, but we also use this member to
8531                    mean "line isn't going to change" (Crafty
8532                    isn't searching, so stats won't change) */
8533                 programStats.line_is_book = 1;
8534
8535                 SendProgramStatsToFrontend( cps, &programStats );
8536
8537                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8538                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8539                     DisplayMove(currentMove - 1);
8540                 }
8541                 return;
8542             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8543                               &time, &nodes, &plylev, &mvleft,
8544                               &mvtot, mvname) >= 5) {
8545                 /* The stat01: line is from Crafty (9.29+) in response
8546                    to the "." command */
8547                 programStats.seen_stat = 1;
8548                 cps->maybeThinking = TRUE;
8549
8550                 if (programStats.got_only_move || !appData.periodicUpdates)
8551                   return;
8552
8553                 programStats.depth = plylev;
8554                 programStats.time = time;
8555                 programStats.nodes = nodes;
8556                 programStats.moves_left = mvleft;
8557                 programStats.nr_moves = mvtot;
8558                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8559                 programStats.ok_to_send = 1;
8560                 programStats.movelist[0] = '\0';
8561
8562                 SendProgramStatsToFrontend( cps, &programStats );
8563
8564                 return;
8565
8566             } else if (strncmp(message,"++",2) == 0) {
8567                 /* Crafty 9.29+ outputs this */
8568                 programStats.got_fail = 2;
8569                 return;
8570
8571             } else if (strncmp(message,"--",2) == 0) {
8572                 /* Crafty 9.29+ outputs this */
8573                 programStats.got_fail = 1;
8574                 return;
8575
8576             } else if (thinkOutput[0] != NULLCHAR &&
8577                        strncmp(message, "    ", 4) == 0) {
8578                 unsigned message_len;
8579
8580                 p = message;
8581                 while (*p && *p == ' ') p++;
8582
8583                 message_len = strlen( p );
8584
8585                 /* [AS] Avoid buffer overflow */
8586                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8587                     strcat(thinkOutput, " ");
8588                     strcat(thinkOutput, p);
8589                 }
8590
8591                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8592                     strcat(programStats.movelist, " ");
8593                     strcat(programStats.movelist, p);
8594                 }
8595
8596                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8597                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8598                     DisplayMove(currentMove - 1);
8599                 }
8600                 return;
8601             }
8602         }
8603         else {
8604             buf1[0] = NULLCHAR;
8605
8606             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8607                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8608             {
8609                 ChessProgramStats cpstats;
8610
8611                 if (plyext != ' ' && plyext != '\t') {
8612                     time *= 100;
8613                 }
8614
8615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8616                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8617                     curscore = -curscore;
8618                 }
8619
8620                 cpstats.depth = plylev;
8621                 cpstats.nodes = nodes;
8622                 cpstats.time = time;
8623                 cpstats.score = curscore;
8624                 cpstats.got_only_move = 0;
8625                 cpstats.movelist[0] = '\0';
8626
8627                 if (buf1[0] != NULLCHAR) {
8628                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8629                 }
8630
8631                 cpstats.ok_to_send = 0;
8632                 cpstats.line_is_book = 0;
8633                 cpstats.nr_moves = 0;
8634                 cpstats.moves_left = 0;
8635
8636                 SendProgramStatsToFrontend( cps, &cpstats );
8637             }
8638         }
8639     }
8640 }
8641
8642
8643 /* Parse a game score from the character string "game", and
8644    record it as the history of the current game.  The game
8645    score is NOT assumed to start from the standard position.
8646    The display is not updated in any way.
8647    */
8648 void
8649 ParseGameHistory(game)
8650      char *game;
8651 {
8652     ChessMove moveType;
8653     int fromX, fromY, toX, toY, boardIndex;
8654     char promoChar;
8655     char *p, *q;
8656     char buf[MSG_SIZ];
8657
8658     if (appData.debugMode)
8659       fprintf(debugFP, "Parsing game history: %s\n", game);
8660
8661     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8662     gameInfo.site = StrSave(appData.icsHost);
8663     gameInfo.date = PGNDate();
8664     gameInfo.round = StrSave("-");
8665
8666     /* Parse out names of players */
8667     while (*game == ' ') game++;
8668     p = buf;
8669     while (*game != ' ') *p++ = *game++;
8670     *p = NULLCHAR;
8671     gameInfo.white = StrSave(buf);
8672     while (*game == ' ') game++;
8673     p = buf;
8674     while (*game != ' ' && *game != '\n') *p++ = *game++;
8675     *p = NULLCHAR;
8676     gameInfo.black = StrSave(buf);
8677
8678     /* Parse moves */
8679     boardIndex = blackPlaysFirst ? 1 : 0;
8680     yynewstr(game);
8681     for (;;) {
8682         yyboardindex = boardIndex;
8683         moveType = (ChessMove) Myylex();
8684         switch (moveType) {
8685           case IllegalMove:             /* maybe suicide chess, etc. */
8686   if (appData.debugMode) {
8687     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8688     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8689     setbuf(debugFP, NULL);
8690   }
8691           case WhitePromotion:
8692           case BlackPromotion:
8693           case WhiteNonPromotion:
8694           case BlackNonPromotion:
8695           case NormalMove:
8696           case WhiteCapturesEnPassant:
8697           case BlackCapturesEnPassant:
8698           case WhiteKingSideCastle:
8699           case WhiteQueenSideCastle:
8700           case BlackKingSideCastle:
8701           case BlackQueenSideCastle:
8702           case WhiteKingSideCastleWild:
8703           case WhiteQueenSideCastleWild:
8704           case BlackKingSideCastleWild:
8705           case BlackQueenSideCastleWild:
8706           /* PUSH Fabien */
8707           case WhiteHSideCastleFR:
8708           case WhiteASideCastleFR:
8709           case BlackHSideCastleFR:
8710           case BlackASideCastleFR:
8711           /* POP Fabien */
8712             fromX = currentMoveString[0] - AAA;
8713             fromY = currentMoveString[1] - ONE;
8714             toX = currentMoveString[2] - AAA;
8715             toY = currentMoveString[3] - ONE;
8716             promoChar = currentMoveString[4];
8717             break;
8718           case WhiteDrop:
8719           case BlackDrop:
8720             fromX = moveType == WhiteDrop ?
8721               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8722             (int) CharToPiece(ToLower(currentMoveString[0]));
8723             fromY = DROP_RANK;
8724             toX = currentMoveString[2] - AAA;
8725             toY = currentMoveString[3] - ONE;
8726             promoChar = NULLCHAR;
8727             break;
8728           case AmbiguousMove:
8729             /* bug? */
8730             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8731   if (appData.debugMode) {
8732     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8733     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8734     setbuf(debugFP, NULL);
8735   }
8736             DisplayError(buf, 0);
8737             return;
8738           case ImpossibleMove:
8739             /* bug? */
8740             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8741   if (appData.debugMode) {
8742     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8743     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8744     setbuf(debugFP, NULL);
8745   }
8746             DisplayError(buf, 0);
8747             return;
8748           case EndOfFile:
8749             if (boardIndex < backwardMostMove) {
8750                 /* Oops, gap.  How did that happen? */
8751                 DisplayError(_("Gap in move list"), 0);
8752                 return;
8753             }
8754             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8755             if (boardIndex > forwardMostMove) {
8756                 forwardMostMove = boardIndex;
8757             }
8758             return;
8759           case ElapsedTime:
8760             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8761                 strcat(parseList[boardIndex-1], " ");
8762                 strcat(parseList[boardIndex-1], yy_text);
8763             }
8764             continue;
8765           case Comment:
8766           case PGNTag:
8767           case NAG:
8768           default:
8769             /* ignore */
8770             continue;
8771           case WhiteWins:
8772           case BlackWins:
8773           case GameIsDrawn:
8774           case GameUnfinished:
8775             if (gameMode == IcsExamining) {
8776                 if (boardIndex < backwardMostMove) {
8777                     /* Oops, gap.  How did that happen? */
8778                     return;
8779                 }
8780                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8781                 return;
8782             }
8783             gameInfo.result = moveType;
8784             p = strchr(yy_text, '{');
8785             if (p == NULL) p = strchr(yy_text, '(');
8786             if (p == NULL) {
8787                 p = yy_text;
8788                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8789             } else {
8790                 q = strchr(p, *p == '{' ? '}' : ')');
8791                 if (q != NULL) *q = NULLCHAR;
8792                 p++;
8793             }
8794             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8795             gameInfo.resultDetails = StrSave(p);
8796             continue;
8797         }
8798         if (boardIndex >= forwardMostMove &&
8799             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8800             backwardMostMove = blackPlaysFirst ? 1 : 0;
8801             return;
8802         }
8803         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8804                                  fromY, fromX, toY, toX, promoChar,
8805                                  parseList[boardIndex]);
8806         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8807         /* currentMoveString is set as a side-effect of yylex */
8808         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8809         strcat(moveList[boardIndex], "\n");
8810         boardIndex++;
8811         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8812         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8813           case MT_NONE:
8814           case MT_STALEMATE:
8815           default:
8816             break;
8817           case MT_CHECK:
8818             if(gameInfo.variant != VariantShogi)
8819                 strcat(parseList[boardIndex - 1], "+");
8820             break;
8821           case MT_CHECKMATE:
8822           case MT_STAINMATE:
8823             strcat(parseList[boardIndex - 1], "#");
8824             break;
8825         }
8826     }
8827 }
8828
8829
8830 /* Apply a move to the given board  */
8831 void
8832 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8833      int fromX, fromY, toX, toY;
8834      int promoChar;
8835      Board board;
8836 {
8837   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8838   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8839
8840     /* [HGM] compute & store e.p. status and castling rights for new position */
8841     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8842
8843       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8844       oldEP = (signed char)board[EP_STATUS];
8845       board[EP_STATUS] = EP_NONE;
8846
8847       if( board[toY][toX] != EmptySquare )
8848            board[EP_STATUS] = EP_CAPTURE;
8849
8850   if (fromY == DROP_RANK) {
8851         /* must be first */
8852         piece = board[toY][toX] = (ChessSquare) fromX;
8853   } else {
8854       int i;
8855
8856       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8857            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8858                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8859       } else
8860       if( board[fromY][fromX] == WhitePawn ) {
8861            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8862                board[EP_STATUS] = EP_PAWN_MOVE;
8863            if( toY-fromY==2) {
8864                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8865                         gameInfo.variant != VariantBerolina || toX < fromX)
8866                       board[EP_STATUS] = toX | berolina;
8867                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8868                         gameInfo.variant != VariantBerolina || toX > fromX)
8869                       board[EP_STATUS] = toX;
8870            }
8871       } else
8872       if( board[fromY][fromX] == BlackPawn ) {
8873            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8874                board[EP_STATUS] = EP_PAWN_MOVE;
8875            if( toY-fromY== -2) {
8876                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8877                         gameInfo.variant != VariantBerolina || toX < fromX)
8878                       board[EP_STATUS] = toX | berolina;
8879                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8880                         gameInfo.variant != VariantBerolina || toX > fromX)
8881                       board[EP_STATUS] = toX;
8882            }
8883        }
8884
8885        for(i=0; i<nrCastlingRights; i++) {
8886            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8887               board[CASTLING][i] == toX   && castlingRank[i] == toY
8888              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8889        }
8890
8891      if (fromX == toX && fromY == toY) return;
8892
8893      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8894      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8895      if(gameInfo.variant == VariantKnightmate)
8896          king += (int) WhiteUnicorn - (int) WhiteKing;
8897
8898     /* Code added by Tord: */
8899     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8900     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8901         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8902       board[fromY][fromX] = EmptySquare;
8903       board[toY][toX] = EmptySquare;
8904       if((toX > fromX) != (piece == WhiteRook)) {
8905         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8906       } else {
8907         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8908       }
8909     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8910                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8911       board[fromY][fromX] = EmptySquare;
8912       board[toY][toX] = EmptySquare;
8913       if((toX > fromX) != (piece == BlackRook)) {
8914         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8915       } else {
8916         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8917       }
8918     /* End of code added by Tord */
8919
8920     } else if (board[fromY][fromX] == king
8921         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8922         && toY == fromY && toX > fromX+1) {
8923         board[fromY][fromX] = EmptySquare;
8924         board[toY][toX] = king;
8925         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8926         board[fromY][BOARD_RGHT-1] = EmptySquare;
8927     } else if (board[fromY][fromX] == king
8928         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8929                && toY == fromY && toX < fromX-1) {
8930         board[fromY][fromX] = EmptySquare;
8931         board[toY][toX] = king;
8932         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8933         board[fromY][BOARD_LEFT] = EmptySquare;
8934     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8935                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8936                && toY >= BOARD_HEIGHT-promoRank
8937                ) {
8938         /* white pawn promotion */
8939         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8940         if (board[toY][toX] == EmptySquare) {
8941             board[toY][toX] = WhiteQueen;
8942         }
8943         if(gameInfo.variant==VariantBughouse ||
8944            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8945             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8946         board[fromY][fromX] = EmptySquare;
8947     } else if ((fromY == BOARD_HEIGHT-4)
8948                && (toX != fromX)
8949                && gameInfo.variant != VariantXiangqi
8950                && gameInfo.variant != VariantBerolina
8951                && (board[fromY][fromX] == WhitePawn)
8952                && (board[toY][toX] == EmptySquare)) {
8953         board[fromY][fromX] = EmptySquare;
8954         board[toY][toX] = WhitePawn;
8955         captured = board[toY - 1][toX];
8956         board[toY - 1][toX] = EmptySquare;
8957     } else if ((fromY == BOARD_HEIGHT-4)
8958                && (toX == fromX)
8959                && gameInfo.variant == VariantBerolina
8960                && (board[fromY][fromX] == WhitePawn)
8961                && (board[toY][toX] == EmptySquare)) {
8962         board[fromY][fromX] = EmptySquare;
8963         board[toY][toX] = WhitePawn;
8964         if(oldEP & EP_BEROLIN_A) {
8965                 captured = board[fromY][fromX-1];
8966                 board[fromY][fromX-1] = EmptySquare;
8967         }else{  captured = board[fromY][fromX+1];
8968                 board[fromY][fromX+1] = EmptySquare;
8969         }
8970     } else if (board[fromY][fromX] == king
8971         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8972                && toY == fromY && toX > fromX+1) {
8973         board[fromY][fromX] = EmptySquare;
8974         board[toY][toX] = king;
8975         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8976         board[fromY][BOARD_RGHT-1] = EmptySquare;
8977     } else if (board[fromY][fromX] == king
8978         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8979                && toY == fromY && toX < fromX-1) {
8980         board[fromY][fromX] = EmptySquare;
8981         board[toY][toX] = king;
8982         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8983         board[fromY][BOARD_LEFT] = EmptySquare;
8984     } else if (fromY == 7 && fromX == 3
8985                && board[fromY][fromX] == BlackKing
8986                && toY == 7 && toX == 5) {
8987         board[fromY][fromX] = EmptySquare;
8988         board[toY][toX] = BlackKing;
8989         board[fromY][7] = EmptySquare;
8990         board[toY][4] = BlackRook;
8991     } else if (fromY == 7 && fromX == 3
8992                && board[fromY][fromX] == BlackKing
8993                && toY == 7 && toX == 1) {
8994         board[fromY][fromX] = EmptySquare;
8995         board[toY][toX] = BlackKing;
8996         board[fromY][0] = EmptySquare;
8997         board[toY][2] = BlackRook;
8998     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8999                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9000                && toY < promoRank
9001                ) {
9002         /* black pawn promotion */
9003         board[toY][toX] = CharToPiece(ToLower(promoChar));
9004         if (board[toY][toX] == EmptySquare) {
9005             board[toY][toX] = BlackQueen;
9006         }
9007         if(gameInfo.variant==VariantBughouse ||
9008            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9009             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9010         board[fromY][fromX] = EmptySquare;
9011     } else if ((fromY == 3)
9012                && (toX != fromX)
9013                && gameInfo.variant != VariantXiangqi
9014                && gameInfo.variant != VariantBerolina
9015                && (board[fromY][fromX] == BlackPawn)
9016                && (board[toY][toX] == EmptySquare)) {
9017         board[fromY][fromX] = EmptySquare;
9018         board[toY][toX] = BlackPawn;
9019         captured = board[toY + 1][toX];
9020         board[toY + 1][toX] = EmptySquare;
9021     } else if ((fromY == 3)
9022                && (toX == fromX)
9023                && gameInfo.variant == VariantBerolina
9024                && (board[fromY][fromX] == BlackPawn)
9025                && (board[toY][toX] == EmptySquare)) {
9026         board[fromY][fromX] = EmptySquare;
9027         board[toY][toX] = BlackPawn;
9028         if(oldEP & EP_BEROLIN_A) {
9029                 captured = board[fromY][fromX-1];
9030                 board[fromY][fromX-1] = EmptySquare;
9031         }else{  captured = board[fromY][fromX+1];
9032                 board[fromY][fromX+1] = EmptySquare;
9033         }
9034     } else {
9035         board[toY][toX] = board[fromY][fromX];
9036         board[fromY][fromX] = EmptySquare;
9037     }
9038   }
9039
9040     if (gameInfo.holdingsWidth != 0) {
9041
9042       /* !!A lot more code needs to be written to support holdings  */
9043       /* [HGM] OK, so I have written it. Holdings are stored in the */
9044       /* penultimate board files, so they are automaticlly stored   */
9045       /* in the game history.                                       */
9046       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9047                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9048         /* Delete from holdings, by decreasing count */
9049         /* and erasing image if necessary            */
9050         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9051         if(p < (int) BlackPawn) { /* white drop */
9052              p -= (int)WhitePawn;
9053                  p = PieceToNumber((ChessSquare)p);
9054              if(p >= gameInfo.holdingsSize) p = 0;
9055              if(--board[p][BOARD_WIDTH-2] <= 0)
9056                   board[p][BOARD_WIDTH-1] = EmptySquare;
9057              if((int)board[p][BOARD_WIDTH-2] < 0)
9058                         board[p][BOARD_WIDTH-2] = 0;
9059         } else {                  /* black drop */
9060              p -= (int)BlackPawn;
9061                  p = PieceToNumber((ChessSquare)p);
9062              if(p >= gameInfo.holdingsSize) p = 0;
9063              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9064                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9065              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9066                         board[BOARD_HEIGHT-1-p][1] = 0;
9067         }
9068       }
9069       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9070           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9071         /* [HGM] holdings: Add to holdings, if holdings exist */
9072         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9073                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9074                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9075         }
9076         p = (int) captured;
9077         if (p >= (int) BlackPawn) {
9078           p -= (int)BlackPawn;
9079           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9080                   /* in Shogi restore piece to its original  first */
9081                   captured = (ChessSquare) (DEMOTED captured);
9082                   p = DEMOTED p;
9083           }
9084           p = PieceToNumber((ChessSquare)p);
9085           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9086           board[p][BOARD_WIDTH-2]++;
9087           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9088         } else {
9089           p -= (int)WhitePawn;
9090           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9091                   captured = (ChessSquare) (DEMOTED captured);
9092                   p = DEMOTED p;
9093           }
9094           p = PieceToNumber((ChessSquare)p);
9095           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9096           board[BOARD_HEIGHT-1-p][1]++;
9097           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9098         }
9099       }
9100     } else if (gameInfo.variant == VariantAtomic) {
9101       if (captured != EmptySquare) {
9102         int y, x;
9103         for (y = toY-1; y <= toY+1; y++) {
9104           for (x = toX-1; x <= toX+1; x++) {
9105             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9106                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9107               board[y][x] = EmptySquare;
9108             }
9109           }
9110         }
9111         board[toY][toX] = EmptySquare;
9112       }
9113     }
9114     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9115         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9116     } else
9117     if(promoChar == '+') {
9118         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9119         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9120     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9121         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9122     }
9123     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9124                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9125         // [HGM] superchess: take promotion piece out of holdings
9126         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9127         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9128             if(!--board[k][BOARD_WIDTH-2])
9129                 board[k][BOARD_WIDTH-1] = EmptySquare;
9130         } else {
9131             if(!--board[BOARD_HEIGHT-1-k][1])
9132                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9133         }
9134     }
9135
9136 }
9137
9138 /* Updates forwardMostMove */
9139 void
9140 MakeMove(fromX, fromY, toX, toY, promoChar)
9141      int fromX, fromY, toX, toY;
9142      int promoChar;
9143 {
9144 //    forwardMostMove++; // [HGM] bare: moved downstream
9145
9146     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9147         int timeLeft; static int lastLoadFlag=0; int king, piece;
9148         piece = boards[forwardMostMove][fromY][fromX];
9149         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9150         if(gameInfo.variant == VariantKnightmate)
9151             king += (int) WhiteUnicorn - (int) WhiteKing;
9152         if(forwardMostMove == 0) {
9153             if(blackPlaysFirst)
9154                 fprintf(serverMoves, "%s;", second.tidy);
9155             fprintf(serverMoves, "%s;", first.tidy);
9156             if(!blackPlaysFirst)
9157                 fprintf(serverMoves, "%s;", second.tidy);
9158         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9159         lastLoadFlag = loadFlag;
9160         // print base move
9161         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9162         // print castling suffix
9163         if( toY == fromY && piece == king ) {
9164             if(toX-fromX > 1)
9165                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9166             if(fromX-toX >1)
9167                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9168         }
9169         // e.p. suffix
9170         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9171              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9172              boards[forwardMostMove][toY][toX] == EmptySquare
9173              && fromX != toX && fromY != toY)
9174                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9175         // promotion suffix
9176         if(promoChar != NULLCHAR)
9177                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9178         if(!loadFlag) {
9179             fprintf(serverMoves, "/%d/%d",
9180                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9181             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9182             else                      timeLeft = blackTimeRemaining/1000;
9183             fprintf(serverMoves, "/%d", timeLeft);
9184         }
9185         fflush(serverMoves);
9186     }
9187
9188     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9189       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9190                         0, 1);
9191       return;
9192     }
9193     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9194     if (commentList[forwardMostMove+1] != NULL) {
9195         free(commentList[forwardMostMove+1]);
9196         commentList[forwardMostMove+1] = NULL;
9197     }
9198     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9199     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9200     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9201     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9202     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9203     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9204     gameInfo.result = GameUnfinished;
9205     if (gameInfo.resultDetails != NULL) {
9206         free(gameInfo.resultDetails);
9207         gameInfo.resultDetails = NULL;
9208     }
9209     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9210                               moveList[forwardMostMove - 1]);
9211     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9212                              PosFlags(forwardMostMove - 1),
9213                              fromY, fromX, toY, toX, promoChar,
9214                              parseList[forwardMostMove - 1]);
9215     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9216       case MT_NONE:
9217       case MT_STALEMATE:
9218       default:
9219         break;
9220       case MT_CHECK:
9221         if(gameInfo.variant != VariantShogi)
9222             strcat(parseList[forwardMostMove - 1], "+");
9223         break;
9224       case MT_CHECKMATE:
9225       case MT_STAINMATE:
9226         strcat(parseList[forwardMostMove - 1], "#");
9227         break;
9228     }
9229     if (appData.debugMode) {
9230         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9231     }
9232
9233 }
9234
9235 /* Updates currentMove if not pausing */
9236 void
9237 ShowMove(fromX, fromY, toX, toY)
9238 {
9239     int instant = (gameMode == PlayFromGameFile) ?
9240         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9241     if(appData.noGUI) return;
9242     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9243         if (!instant) {
9244             if (forwardMostMove == currentMove + 1) {
9245                 AnimateMove(boards[forwardMostMove - 1],
9246                             fromX, fromY, toX, toY);
9247             }
9248             if (appData.highlightLastMove) {
9249                 SetHighlights(fromX, fromY, toX, toY);
9250             }
9251         }
9252         currentMove = forwardMostMove;
9253     }
9254
9255     if (instant) return;
9256
9257     DisplayMove(currentMove - 1);
9258     DrawPosition(FALSE, boards[currentMove]);
9259     DisplayBothClocks();
9260     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9261     DisplayBook(currentMove);
9262 }
9263
9264 void SendEgtPath(ChessProgramState *cps)
9265 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9266         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9267
9268         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9269
9270         while(*p) {
9271             char c, *q = name+1, *r, *s;
9272
9273             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9274             while(*p && *p != ',') *q++ = *p++;
9275             *q++ = ':'; *q = 0;
9276             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9277                 strcmp(name, ",nalimov:") == 0 ) {
9278                 // take nalimov path from the menu-changeable option first, if it is defined
9279               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9280                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9281             } else
9282             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9283                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9284                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9285                 s = r = StrStr(s, ":") + 1; // beginning of path info
9286                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9287                 c = *r; *r = 0;             // temporarily null-terminate path info
9288                     *--q = 0;               // strip of trailig ':' from name
9289                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9290                 *r = c;
9291                 SendToProgram(buf,cps);     // send egtbpath command for this format
9292             }
9293             if(*p == ',') p++; // read away comma to position for next format name
9294         }
9295 }
9296
9297 void
9298 InitChessProgram(cps, setup)
9299      ChessProgramState *cps;
9300      int setup; /* [HGM] needed to setup FRC opening position */
9301 {
9302     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9303     if (appData.noChessProgram) return;
9304     hintRequested = FALSE;
9305     bookRequested = FALSE;
9306
9307     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9308     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9309     if(cps->memSize) { /* [HGM] memory */
9310       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9311         SendToProgram(buf, cps);
9312     }
9313     SendEgtPath(cps); /* [HGM] EGT */
9314     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9315       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9316         SendToProgram(buf, cps);
9317     }
9318
9319     SendToProgram(cps->initString, cps);
9320     if (gameInfo.variant != VariantNormal &&
9321         gameInfo.variant != VariantLoadable
9322         /* [HGM] also send variant if board size non-standard */
9323         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9324                                             ) {
9325       char *v = VariantName(gameInfo.variant);
9326       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9327         /* [HGM] in protocol 1 we have to assume all variants valid */
9328         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9329         DisplayFatalError(buf, 0, 1);
9330         return;
9331       }
9332
9333       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9334       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9335       if( gameInfo.variant == VariantXiangqi )
9336            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9337       if( gameInfo.variant == VariantShogi )
9338            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9339       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9340            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9341       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9342           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9343            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9344       if( gameInfo.variant == VariantCourier )
9345            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9346       if( gameInfo.variant == VariantSuper )
9347            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9348       if( gameInfo.variant == VariantGreat )
9349            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9350       if( gameInfo.variant == VariantSChess )
9351            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9352
9353       if(overruled) {
9354         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9355                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9356            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9357            if(StrStr(cps->variants, b) == NULL) {
9358                // specific sized variant not known, check if general sizing allowed
9359                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9360                    if(StrStr(cps->variants, "boardsize") == NULL) {
9361                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9362                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9363                        DisplayFatalError(buf, 0, 1);
9364                        return;
9365                    }
9366                    /* [HGM] here we really should compare with the maximum supported board size */
9367                }
9368            }
9369       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9370       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9371       SendToProgram(buf, cps);
9372     }
9373     currentlyInitializedVariant = gameInfo.variant;
9374
9375     /* [HGM] send opening position in FRC to first engine */
9376     if(setup) {
9377           SendToProgram("force\n", cps);
9378           SendBoard(cps, 0);
9379           /* engine is now in force mode! Set flag to wake it up after first move. */
9380           setboardSpoiledMachineBlack = 1;
9381     }
9382
9383     if (cps->sendICS) {
9384       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9385       SendToProgram(buf, cps);
9386     }
9387     cps->maybeThinking = FALSE;
9388     cps->offeredDraw = 0;
9389     if (!appData.icsActive) {
9390         SendTimeControl(cps, movesPerSession, timeControl,
9391                         timeIncrement, appData.searchDepth,
9392                         searchTime);
9393     }
9394     if (appData.showThinking
9395         // [HGM] thinking: four options require thinking output to be sent
9396         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9397                                 ) {
9398         SendToProgram("post\n", cps);
9399     }
9400     SendToProgram("hard\n", cps);
9401     if (!appData.ponderNextMove) {
9402         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9403            it without being sure what state we are in first.  "hard"
9404            is not a toggle, so that one is OK.
9405          */
9406         SendToProgram("easy\n", cps);
9407     }
9408     if (cps->usePing) {
9409       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9410       SendToProgram(buf, cps);
9411     }
9412     cps->initDone = TRUE;
9413 }
9414
9415
9416 void
9417 StartChessProgram(cps)
9418      ChessProgramState *cps;
9419 {
9420     char buf[MSG_SIZ];
9421     int err;
9422
9423     if (appData.noChessProgram) return;
9424     cps->initDone = FALSE;
9425
9426     if (strcmp(cps->host, "localhost") == 0) {
9427         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9428     } else if (*appData.remoteShell == NULLCHAR) {
9429         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9430     } else {
9431         if (*appData.remoteUser == NULLCHAR) {
9432           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9433                     cps->program);
9434         } else {
9435           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9436                     cps->host, appData.remoteUser, cps->program);
9437         }
9438         err = StartChildProcess(buf, "", &cps->pr);
9439     }
9440
9441     if (err != 0) {
9442       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9443         DisplayFatalError(buf, err, 1);
9444         cps->pr = NoProc;
9445         cps->isr = NULL;
9446         return;
9447     }
9448
9449     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9450     if (cps->protocolVersion > 1) {
9451       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9452       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9453       cps->comboCnt = 0;  //                and values of combo boxes
9454       SendToProgram(buf, cps);
9455     } else {
9456       SendToProgram("xboard\n", cps);
9457     }
9458 }
9459
9460 void
9461 TwoMachinesEventIfReady P((void))
9462 {
9463   static int curMess = 0;
9464   if (first.lastPing != first.lastPong) {
9465     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9466     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9467     return;
9468   }
9469   if (second.lastPing != second.lastPong) {
9470     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9471     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9472     return;
9473   }
9474   DisplayMessage("", ""); curMess = 0;
9475   ThawUI();
9476   TwoMachinesEvent();
9477 }
9478
9479 int
9480 CreateTourney(char *name)
9481 {
9482         FILE *f;
9483         if(name[0] == NULLCHAR) return 0;
9484         f = fopen(appData.tourneyFile, "r");
9485         if(f) { // file exists
9486             ParseArgsFromFile(f); // parse it
9487         } else {
9488             f = fopen(appData.tourneyFile, "w");
9489             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9490                 // create a file with tournament description
9491                 fprintf(f, "-participants {%s}\n", appData.participants);
9492                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9493                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9494                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9495                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9496                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9497                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9498                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9499                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9500                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9501                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9502                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9503                 if(searchTime > 0)
9504                         fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9505                 else {
9506                         fprintf(f, "-mps %d\n", appData.movesPerSession);
9507                         fprintf(f, "-tc %s\n", appData.timeControl);
9508                         fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9509                 }
9510                 fprintf(f, "-results \"\"\n");
9511             }
9512         }
9513         fclose(f);
9514         appData.noChessProgram = FALSE;
9515         appData.clockMode = TRUE;
9516         SetGNUMode();
9517         return 1;
9518 }
9519
9520 #define MAXENGINES 1000
9521 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9522
9523 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9524 {
9525     char buf[MSG_SIZ], *p, *q;
9526     int i=1;
9527     while(*names) {
9528         p = names; q = buf;
9529         while(*p && *p != '\n') *q++ = *p++;
9530         *q = 0;
9531         if(engineList[i]) free(engineList[i]);
9532         engineList[i] = strdup(buf);
9533         if(*p == '\n') p++;
9534         TidyProgramName(engineList[i], "localhost", buf);
9535         if(engineMnemonic[i]) free(engineMnemonic[i]);
9536         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9537             strcat(buf, " (");
9538             sscanf(q + 8, "%s", buf + strlen(buf));
9539             strcat(buf, ")");
9540         }
9541         engineMnemonic[i] = strdup(buf);
9542         names = p; i++;
9543       if(i > MAXENGINES - 2) break;
9544     }
9545     engineList[i] = NULL;
9546 }
9547
9548 // following implemented as macro to avoid type limitations
9549 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9550
9551 void SwapEngines(int n)
9552 {   // swap settings for first engine and other engine (so far only some selected options)
9553     int h;
9554     char *p;
9555     if(n == 0) return;
9556     SWAP(directory, p)
9557     SWAP(chessProgram, p)
9558     SWAP(isUCI, h)
9559     SWAP(hasOwnBookUCI, h)
9560     SWAP(protocolVersion, h)
9561     SWAP(reuse, h)
9562     SWAP(scoreIsAbsolute, h)
9563     SWAP(timeOdds, h)
9564     SWAP(logo, p)
9565     SWAP(pgnName, p)
9566 }
9567
9568 void
9569 SetPlayer(int player)
9570 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9571     int i;
9572     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9573     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9574     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9575     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9576     if(mnemonic[i]) {
9577         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9578         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9579         ParseArgsFromString(buf);
9580     }
9581     free(engineName);
9582 }
9583
9584 int
9585 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9586 {   // determine players from game number
9587     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9588
9589     if(appData.tourneyType == 0) {
9590         roundsPerCycle = (nPlayers - 1) | 1;
9591         pairingsPerRound = nPlayers / 2;
9592     } else if(appData.tourneyType > 0) {
9593         roundsPerCycle = nPlayers - appData.tourneyType;
9594         pairingsPerRound = appData.tourneyType;
9595     }
9596     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9597     gamesPerCycle = gamesPerRound * roundsPerCycle;
9598     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9599     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9600     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9601     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9602     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9603     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9604
9605     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9606     if(appData.roundSync) *syncInterval = gamesPerRound;
9607
9608     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9609
9610     if(appData.tourneyType == 0) {
9611         if(curPairing == (nPlayers-1)/2 ) {
9612             *whitePlayer = curRound;
9613             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9614         } else {
9615             *whitePlayer = curRound - pairingsPerRound + curPairing;
9616             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9617             *blackPlayer = curRound + pairingsPerRound - curPairing;
9618             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9619         }
9620     } else if(appData.tourneyType > 0) {
9621         *whitePlayer = curPairing;
9622         *blackPlayer = curRound + appData.tourneyType;
9623     }
9624
9625     // take care of white/black alternation per round. 
9626     // For cycles and games this is already taken care of by default, derived from matchGame!
9627     return curRound & 1;
9628 }
9629
9630 int
9631 NextTourneyGame(int nr, int *swapColors)
9632 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9633     char *p, *q;
9634     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9635     FILE *tf;
9636     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9637     tf = fopen(appData.tourneyFile, "r");
9638     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9639     ParseArgsFromFile(tf); fclose(tf);
9640     InitTimeControls(); // TC might be altered from tourney file
9641
9642     p = appData.participants;
9643     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9644     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9645
9646     if(syncInterval) {
9647         p = q = appData.results;
9648         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9649         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9650             DisplayMessage(_("Waiting for other game(s)"),"");
9651             waitingForGame = TRUE;
9652             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9653             return 0;
9654         }
9655         waitingForGame = FALSE;
9656     }
9657
9658     if(first.pr != NoProc) return 1; // engines already loaded
9659
9660     // redefine engines, engine dir, etc.
9661     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9662     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9663     SwapEngines(1);
9664     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9665     SwapEngines(1);         // and make that valid for second engine by swapping
9666     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9667     InitEngine(&second, 1);
9668     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9669     return 1;
9670 }
9671
9672 void
9673 NextMatchGame()
9674 {   // performs game initialization that does not invoke engines, and then tries to start the game
9675     int firstWhite, swapColors = 0;
9676     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9677     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9678     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9679     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9680     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9681     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9682     Reset(FALSE, first.pr != NoProc);
9683     appData.noChessProgram = FALSE;
9684     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9685     TwoMachinesEvent();
9686 }
9687
9688 void UserAdjudicationEvent( int result )
9689 {
9690     ChessMove gameResult = GameIsDrawn;
9691
9692     if( result > 0 ) {
9693         gameResult = WhiteWins;
9694     }
9695     else if( result < 0 ) {
9696         gameResult = BlackWins;
9697     }
9698
9699     if( gameMode == TwoMachinesPlay ) {
9700         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9701     }
9702 }
9703
9704
9705 // [HGM] save: calculate checksum of game to make games easily identifiable
9706 int StringCheckSum(char *s)
9707 {
9708         int i = 0;
9709         if(s==NULL) return 0;
9710         while(*s) i = i*259 + *s++;
9711         return i;
9712 }
9713
9714 int GameCheckSum()
9715 {
9716         int i, sum=0;
9717         for(i=backwardMostMove; i<forwardMostMove; i++) {
9718                 sum += pvInfoList[i].depth;
9719                 sum += StringCheckSum(parseList[i]);
9720                 sum += StringCheckSum(commentList[i]);
9721                 sum *= 261;
9722         }
9723         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9724         return sum + StringCheckSum(commentList[i]);
9725 } // end of save patch
9726
9727 void
9728 GameEnds(result, resultDetails, whosays)
9729      ChessMove result;
9730      char *resultDetails;
9731      int whosays;
9732 {
9733     GameMode nextGameMode;
9734     int isIcsGame;
9735     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9736
9737     if(endingGame) return; /* [HGM] crash: forbid recursion */
9738     endingGame = 1;
9739     if(twoBoards) { // [HGM] dual: switch back to one board
9740         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9741         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9742     }
9743     if (appData.debugMode) {
9744       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9745               result, resultDetails ? resultDetails : "(null)", whosays);
9746     }
9747
9748     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9749
9750     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9751         /* If we are playing on ICS, the server decides when the
9752            game is over, but the engine can offer to draw, claim
9753            a draw, or resign.
9754          */
9755 #if ZIPPY
9756         if (appData.zippyPlay && first.initDone) {
9757             if (result == GameIsDrawn) {
9758                 /* In case draw still needs to be claimed */
9759                 SendToICS(ics_prefix);
9760                 SendToICS("draw\n");
9761             } else if (StrCaseStr(resultDetails, "resign")) {
9762                 SendToICS(ics_prefix);
9763                 SendToICS("resign\n");
9764             }
9765         }
9766 #endif
9767         endingGame = 0; /* [HGM] crash */
9768         return;
9769     }
9770
9771     /* If we're loading the game from a file, stop */
9772     if (whosays == GE_FILE) {
9773       (void) StopLoadGameTimer();
9774       gameFileFP = NULL;
9775     }
9776
9777     /* Cancel draw offers */
9778     first.offeredDraw = second.offeredDraw = 0;
9779
9780     /* If this is an ICS game, only ICS can really say it's done;
9781        if not, anyone can. */
9782     isIcsGame = (gameMode == IcsPlayingWhite ||
9783                  gameMode == IcsPlayingBlack ||
9784                  gameMode == IcsObserving    ||
9785                  gameMode == IcsExamining);
9786
9787     if (!isIcsGame || whosays == GE_ICS) {
9788         /* OK -- not an ICS game, or ICS said it was done */
9789         StopClocks();
9790         if (!isIcsGame && !appData.noChessProgram)
9791           SetUserThinkingEnables();
9792
9793         /* [HGM] if a machine claims the game end we verify this claim */
9794         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9795             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9796                 char claimer;
9797                 ChessMove trueResult = (ChessMove) -1;
9798
9799                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9800                                             first.twoMachinesColor[0] :
9801                                             second.twoMachinesColor[0] ;
9802
9803                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9804                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9805                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9806                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9807                 } else
9808                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9809                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9810                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9811                 } else
9812                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9813                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9814                 }
9815
9816                 // now verify win claims, but not in drop games, as we don't understand those yet
9817                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9818                                                  || gameInfo.variant == VariantGreat) &&
9819                     (result == WhiteWins && claimer == 'w' ||
9820                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9821                       if (appData.debugMode) {
9822                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9823                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9824                       }
9825                       if(result != trueResult) {
9826                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9827                               result = claimer == 'w' ? BlackWins : WhiteWins;
9828                               resultDetails = buf;
9829                       }
9830                 } else
9831                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9832                     && (forwardMostMove <= backwardMostMove ||
9833                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9834                         (claimer=='b')==(forwardMostMove&1))
9835                                                                                   ) {
9836                       /* [HGM] verify: draws that were not flagged are false claims */
9837                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9838                       result = claimer == 'w' ? BlackWins : WhiteWins;
9839                       resultDetails = buf;
9840                 }
9841                 /* (Claiming a loss is accepted no questions asked!) */
9842             }
9843             /* [HGM] bare: don't allow bare King to win */
9844             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9845                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9846                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9847                && result != GameIsDrawn)
9848             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9849                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9850                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9851                         if(p >= 0 && p <= (int)WhiteKing) k++;
9852                 }
9853                 if (appData.debugMode) {
9854                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9855                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9856                 }
9857                 if(k <= 1) {
9858                         result = GameIsDrawn;
9859                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9860                         resultDetails = buf;
9861                 }
9862             }
9863         }
9864
9865
9866         if(serverMoves != NULL && !loadFlag) { char c = '=';
9867             if(result==WhiteWins) c = '+';
9868             if(result==BlackWins) c = '-';
9869             if(resultDetails != NULL)
9870                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9871         }
9872         if (resultDetails != NULL) {
9873             gameInfo.result = result;
9874             gameInfo.resultDetails = StrSave(resultDetails);
9875
9876             /* display last move only if game was not loaded from file */
9877             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9878                 DisplayMove(currentMove - 1);
9879
9880             if (forwardMostMove != 0) {
9881                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9882                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9883                                                                 ) {
9884                     if (*appData.saveGameFile != NULLCHAR) {
9885                         SaveGameToFile(appData.saveGameFile, TRUE);
9886                     } else if (appData.autoSaveGames) {
9887                         AutoSaveGame();
9888                     }
9889                     if (*appData.savePositionFile != NULLCHAR) {
9890                         SavePositionToFile(appData.savePositionFile);
9891                     }
9892                 }
9893             }
9894
9895             /* Tell program how game ended in case it is learning */
9896             /* [HGM] Moved this to after saving the PGN, just in case */
9897             /* engine died and we got here through time loss. In that */
9898             /* case we will get a fatal error writing the pipe, which */
9899             /* would otherwise lose us the PGN.                       */
9900             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9901             /* output during GameEnds should never be fatal anymore   */
9902             if (gameMode == MachinePlaysWhite ||
9903                 gameMode == MachinePlaysBlack ||
9904                 gameMode == TwoMachinesPlay ||
9905                 gameMode == IcsPlayingWhite ||
9906                 gameMode == IcsPlayingBlack ||
9907                 gameMode == BeginningOfGame) {
9908                 char buf[MSG_SIZ];
9909                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9910                         resultDetails);
9911                 if (first.pr != NoProc) {
9912                     SendToProgram(buf, &first);
9913                 }
9914                 if (second.pr != NoProc &&
9915                     gameMode == TwoMachinesPlay) {
9916                     SendToProgram(buf, &second);
9917                 }
9918             }
9919         }
9920
9921         if (appData.icsActive) {
9922             if (appData.quietPlay &&
9923                 (gameMode == IcsPlayingWhite ||
9924                  gameMode == IcsPlayingBlack)) {
9925                 SendToICS(ics_prefix);
9926                 SendToICS("set shout 1\n");
9927             }
9928             nextGameMode = IcsIdle;
9929             ics_user_moved = FALSE;
9930             /* clean up premove.  It's ugly when the game has ended and the
9931              * premove highlights are still on the board.
9932              */
9933             if (gotPremove) {
9934               gotPremove = FALSE;
9935               ClearPremoveHighlights();
9936               DrawPosition(FALSE, boards[currentMove]);
9937             }
9938             if (whosays == GE_ICS) {
9939                 switch (result) {
9940                 case WhiteWins:
9941                     if (gameMode == IcsPlayingWhite)
9942                         PlayIcsWinSound();
9943                     else if(gameMode == IcsPlayingBlack)
9944                         PlayIcsLossSound();
9945                     break;
9946                 case BlackWins:
9947                     if (gameMode == IcsPlayingBlack)
9948                         PlayIcsWinSound();
9949                     else if(gameMode == IcsPlayingWhite)
9950                         PlayIcsLossSound();
9951                     break;
9952                 case GameIsDrawn:
9953                     PlayIcsDrawSound();
9954                     break;
9955                 default:
9956                     PlayIcsUnfinishedSound();
9957                 }
9958             }
9959         } else if (gameMode == EditGame ||
9960                    gameMode == PlayFromGameFile ||
9961                    gameMode == AnalyzeMode ||
9962                    gameMode == AnalyzeFile) {
9963             nextGameMode = gameMode;
9964         } else {
9965             nextGameMode = EndOfGame;
9966         }
9967         pausing = FALSE;
9968         ModeHighlight();
9969     } else {
9970         nextGameMode = gameMode;
9971     }
9972
9973     if (appData.noChessProgram) {
9974         gameMode = nextGameMode;
9975         ModeHighlight();
9976         endingGame = 0; /* [HGM] crash */
9977         return;
9978     }
9979
9980     if (first.reuse) {
9981         /* Put first chess program into idle state */
9982         if (first.pr != NoProc &&
9983             (gameMode == MachinePlaysWhite ||
9984              gameMode == MachinePlaysBlack ||
9985              gameMode == TwoMachinesPlay ||
9986              gameMode == IcsPlayingWhite ||
9987              gameMode == IcsPlayingBlack ||
9988              gameMode == BeginningOfGame)) {
9989             SendToProgram("force\n", &first);
9990             if (first.usePing) {
9991               char buf[MSG_SIZ];
9992               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9993               SendToProgram(buf, &first);
9994             }
9995         }
9996     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9997         /* Kill off first chess program */
9998         if (first.isr != NULL)
9999           RemoveInputSource(first.isr);
10000         first.isr = NULL;
10001
10002         if (first.pr != NoProc) {
10003             ExitAnalyzeMode();
10004             DoSleep( appData.delayBeforeQuit );
10005             SendToProgram("quit\n", &first);
10006             DoSleep( appData.delayAfterQuit );
10007             DestroyChildProcess(first.pr, first.useSigterm);
10008         }
10009         first.pr = NoProc;
10010     }
10011     if (second.reuse) {
10012         /* Put second chess program into idle state */
10013         if (second.pr != NoProc &&
10014             gameMode == TwoMachinesPlay) {
10015             SendToProgram("force\n", &second);
10016             if (second.usePing) {
10017               char buf[MSG_SIZ];
10018               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10019               SendToProgram(buf, &second);
10020             }
10021         }
10022     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10023         /* Kill off second chess program */
10024         if (second.isr != NULL)
10025           RemoveInputSource(second.isr);
10026         second.isr = NULL;
10027
10028         if (second.pr != NoProc) {
10029             DoSleep( appData.delayBeforeQuit );
10030             SendToProgram("quit\n", &second);
10031             DoSleep( appData.delayAfterQuit );
10032             DestroyChildProcess(second.pr, second.useSigterm);
10033         }
10034         second.pr = NoProc;
10035     }
10036
10037     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10038         char resChar = '=';
10039         switch (result) {
10040         case WhiteWins:
10041           resChar = '+';
10042           if (first.twoMachinesColor[0] == 'w') {
10043             first.matchWins++;
10044           } else {
10045             second.matchWins++;
10046           }
10047           break;
10048         case BlackWins:
10049           resChar = '-';
10050           if (first.twoMachinesColor[0] == 'b') {
10051             first.matchWins++;
10052           } else {
10053             second.matchWins++;
10054           }
10055           break;
10056         case GameUnfinished:
10057           resChar = ' ';
10058         default:
10059           break;
10060         }
10061
10062         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10063         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10064             ReserveGame(nextGame, resChar); // sets nextGame
10065             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10066         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10067
10068         if (nextGame <= appData.matchGames && !abortMatch) {
10069             gameMode = nextGameMode;
10070             matchGame = nextGame; // this will be overruled in tourney mode!
10071             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10072             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10073             endingGame = 0; /* [HGM] crash */
10074             return;
10075         } else {
10076             gameMode = nextGameMode;
10077             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10078                      first.tidy, second.tidy,
10079                      first.matchWins, second.matchWins,
10080                      appData.matchGames - (first.matchWins + second.matchWins));
10081             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10082             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10083                 first.twoMachinesColor = "black\n";
10084                 second.twoMachinesColor = "white\n";
10085             } else {
10086                 first.twoMachinesColor = "white\n";
10087                 second.twoMachinesColor = "black\n";
10088             }
10089         }
10090     }
10091     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10092         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10093       ExitAnalyzeMode();
10094     gameMode = nextGameMode;
10095     ModeHighlight();
10096     endingGame = 0;  /* [HGM] crash */
10097     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10098         if(matchMode == TRUE) { // match through command line: exit with or without popup
10099             if(ranking) {
10100                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10101                 else ExitEvent(0);
10102             } else DisplayFatalError(buf, 0, 0);
10103         } else { // match through menu; just stop, with or without popup
10104             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10105             if(ranking){
10106                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10107             } else DisplayNote(buf);
10108       }
10109       if(ranking) free(ranking);
10110     }
10111 }
10112
10113 /* Assumes program was just initialized (initString sent).
10114    Leaves program in force mode. */
10115 void
10116 FeedMovesToProgram(cps, upto)
10117      ChessProgramState *cps;
10118      int upto;
10119 {
10120     int i;
10121
10122     if (appData.debugMode)
10123       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10124               startedFromSetupPosition ? "position and " : "",
10125               backwardMostMove, upto, cps->which);
10126     if(currentlyInitializedVariant != gameInfo.variant) {
10127       char buf[MSG_SIZ];
10128         // [HGM] variantswitch: make engine aware of new variant
10129         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10130                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10131         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10132         SendToProgram(buf, cps);
10133         currentlyInitializedVariant = gameInfo.variant;
10134     }
10135     SendToProgram("force\n", cps);
10136     if (startedFromSetupPosition) {
10137         SendBoard(cps, backwardMostMove);
10138     if (appData.debugMode) {
10139         fprintf(debugFP, "feedMoves\n");
10140     }
10141     }
10142     for (i = backwardMostMove; i < upto; i++) {
10143         SendMoveToProgram(i, cps);
10144     }
10145 }
10146
10147
10148 int
10149 ResurrectChessProgram()
10150 {
10151      /* The chess program may have exited.
10152         If so, restart it and feed it all the moves made so far. */
10153     static int doInit = 0;
10154
10155     if (appData.noChessProgram) return 1;
10156
10157     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10158         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10159         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10160         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10161     } else {
10162         if (first.pr != NoProc) return 1;
10163         StartChessProgram(&first);
10164     }
10165     InitChessProgram(&first, FALSE);
10166     FeedMovesToProgram(&first, currentMove);
10167
10168     if (!first.sendTime) {
10169         /* can't tell gnuchess what its clock should read,
10170            so we bow to its notion. */
10171         ResetClocks();
10172         timeRemaining[0][currentMove] = whiteTimeRemaining;
10173         timeRemaining[1][currentMove] = blackTimeRemaining;
10174     }
10175
10176     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10177                 appData.icsEngineAnalyze) && first.analysisSupport) {
10178       SendToProgram("analyze\n", &first);
10179       first.analyzing = TRUE;
10180     }
10181     return 1;
10182 }
10183
10184 /*
10185  * Button procedures
10186  */
10187 void
10188 Reset(redraw, init)
10189      int redraw, init;
10190 {
10191     int i;
10192
10193     if (appData.debugMode) {
10194         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10195                 redraw, init, gameMode);
10196     }
10197     CleanupTail(); // [HGM] vari: delete any stored variations
10198     pausing = pauseExamInvalid = FALSE;
10199     startedFromSetupPosition = blackPlaysFirst = FALSE;
10200     firstMove = TRUE;
10201     whiteFlag = blackFlag = FALSE;
10202     userOfferedDraw = FALSE;
10203     hintRequested = bookRequested = FALSE;
10204     first.maybeThinking = FALSE;
10205     second.maybeThinking = FALSE;
10206     first.bookSuspend = FALSE; // [HGM] book
10207     second.bookSuspend = FALSE;
10208     thinkOutput[0] = NULLCHAR;
10209     lastHint[0] = NULLCHAR;
10210     ClearGameInfo(&gameInfo);
10211     gameInfo.variant = StringToVariant(appData.variant);
10212     ics_user_moved = ics_clock_paused = FALSE;
10213     ics_getting_history = H_FALSE;
10214     ics_gamenum = -1;
10215     white_holding[0] = black_holding[0] = NULLCHAR;
10216     ClearProgramStats();
10217     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10218
10219     ResetFrontEnd();
10220     ClearHighlights();
10221     flipView = appData.flipView;
10222     ClearPremoveHighlights();
10223     gotPremove = FALSE;
10224     alarmSounded = FALSE;
10225
10226     GameEnds(EndOfFile, NULL, GE_PLAYER);
10227     if(appData.serverMovesName != NULL) {
10228         /* [HGM] prepare to make moves file for broadcasting */
10229         clock_t t = clock();
10230         if(serverMoves != NULL) fclose(serverMoves);
10231         serverMoves = fopen(appData.serverMovesName, "r");
10232         if(serverMoves != NULL) {
10233             fclose(serverMoves);
10234             /* delay 15 sec before overwriting, so all clients can see end */
10235             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10236         }
10237         serverMoves = fopen(appData.serverMovesName, "w");
10238     }
10239
10240     ExitAnalyzeMode();
10241     gameMode = BeginningOfGame;
10242     ModeHighlight();
10243     if(appData.icsActive) gameInfo.variant = VariantNormal;
10244     currentMove = forwardMostMove = backwardMostMove = 0;
10245     InitPosition(redraw);
10246     for (i = 0; i < MAX_MOVES; i++) {
10247         if (commentList[i] != NULL) {
10248             free(commentList[i]);
10249             commentList[i] = NULL;
10250         }
10251     }
10252     ResetClocks();
10253     timeRemaining[0][0] = whiteTimeRemaining;
10254     timeRemaining[1][0] = blackTimeRemaining;
10255
10256     if (first.pr == NULL) {
10257         StartChessProgram(&first);
10258     }
10259     if (init) {
10260             InitChessProgram(&first, startedFromSetupPosition);
10261     }
10262     DisplayTitle("");
10263     DisplayMessage("", "");
10264     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10265     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10266 }
10267
10268 void
10269 AutoPlayGameLoop()
10270 {
10271     for (;;) {
10272         if (!AutoPlayOneMove())
10273           return;
10274         if (matchMode || appData.timeDelay == 0)
10275           continue;
10276         if (appData.timeDelay < 0)
10277           return;
10278         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10279         break;
10280     }
10281 }
10282
10283
10284 int
10285 AutoPlayOneMove()
10286 {
10287     int fromX, fromY, toX, toY;
10288
10289     if (appData.debugMode) {
10290       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10291     }
10292
10293     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10294       return FALSE;
10295
10296     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10297       pvInfoList[currentMove].depth = programStats.depth;
10298       pvInfoList[currentMove].score = programStats.score;
10299       pvInfoList[currentMove].time  = 0;
10300       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10301     }
10302
10303     if (currentMove >= forwardMostMove) {
10304       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10305       gameMode = EditGame;
10306       ModeHighlight();
10307
10308       /* [AS] Clear current move marker at the end of a game */
10309       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10310
10311       return FALSE;
10312     }
10313
10314     toX = moveList[currentMove][2] - AAA;
10315     toY = moveList[currentMove][3] - ONE;
10316
10317     if (moveList[currentMove][1] == '@') {
10318         if (appData.highlightLastMove) {
10319             SetHighlights(-1, -1, toX, toY);
10320         }
10321     } else {
10322         fromX = moveList[currentMove][0] - AAA;
10323         fromY = moveList[currentMove][1] - ONE;
10324
10325         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10326
10327         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10328
10329         if (appData.highlightLastMove) {
10330             SetHighlights(fromX, fromY, toX, toY);
10331         }
10332     }
10333     DisplayMove(currentMove);
10334     SendMoveToProgram(currentMove++, &first);
10335     DisplayBothClocks();
10336     DrawPosition(FALSE, boards[currentMove]);
10337     // [HGM] PV info: always display, routine tests if empty
10338     DisplayComment(currentMove - 1, commentList[currentMove]);
10339     return TRUE;
10340 }
10341
10342
10343 int
10344 LoadGameOneMove(readAhead)
10345      ChessMove readAhead;
10346 {
10347     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10348     char promoChar = NULLCHAR;
10349     ChessMove moveType;
10350     char move[MSG_SIZ];
10351     char *p, *q;
10352
10353     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10354         gameMode != AnalyzeMode && gameMode != Training) {
10355         gameFileFP = NULL;
10356         return FALSE;
10357     }
10358
10359     yyboardindex = forwardMostMove;
10360     if (readAhead != EndOfFile) {
10361       moveType = readAhead;
10362     } else {
10363       if (gameFileFP == NULL)
10364           return FALSE;
10365       moveType = (ChessMove) Myylex();
10366     }
10367
10368     done = FALSE;
10369     switch (moveType) {
10370       case Comment:
10371         if (appData.debugMode)
10372           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10373         p = yy_text;
10374
10375         /* append the comment but don't display it */
10376         AppendComment(currentMove, p, FALSE);
10377         return TRUE;
10378
10379       case WhiteCapturesEnPassant:
10380       case BlackCapturesEnPassant:
10381       case WhitePromotion:
10382       case BlackPromotion:
10383       case WhiteNonPromotion:
10384       case BlackNonPromotion:
10385       case NormalMove:
10386       case WhiteKingSideCastle:
10387       case WhiteQueenSideCastle:
10388       case BlackKingSideCastle:
10389       case BlackQueenSideCastle:
10390       case WhiteKingSideCastleWild:
10391       case WhiteQueenSideCastleWild:
10392       case BlackKingSideCastleWild:
10393       case BlackQueenSideCastleWild:
10394       /* PUSH Fabien */
10395       case WhiteHSideCastleFR:
10396       case WhiteASideCastleFR:
10397       case BlackHSideCastleFR:
10398       case BlackASideCastleFR:
10399       /* POP Fabien */
10400         if (appData.debugMode)
10401           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10402         fromX = currentMoveString[0] - AAA;
10403         fromY = currentMoveString[1] - ONE;
10404         toX = currentMoveString[2] - AAA;
10405         toY = currentMoveString[3] - ONE;
10406         promoChar = currentMoveString[4];
10407         break;
10408
10409       case WhiteDrop:
10410       case BlackDrop:
10411         if (appData.debugMode)
10412           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10413         fromX = moveType == WhiteDrop ?
10414           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10415         (int) CharToPiece(ToLower(currentMoveString[0]));
10416         fromY = DROP_RANK;
10417         toX = currentMoveString[2] - AAA;
10418         toY = currentMoveString[3] - ONE;
10419         break;
10420
10421       case WhiteWins:
10422       case BlackWins:
10423       case GameIsDrawn:
10424       case GameUnfinished:
10425         if (appData.debugMode)
10426           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10427         p = strchr(yy_text, '{');
10428         if (p == NULL) p = strchr(yy_text, '(');
10429         if (p == NULL) {
10430             p = yy_text;
10431             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10432         } else {
10433             q = strchr(p, *p == '{' ? '}' : ')');
10434             if (q != NULL) *q = NULLCHAR;
10435             p++;
10436         }
10437         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10438         GameEnds(moveType, p, GE_FILE);
10439         done = TRUE;
10440         if (cmailMsgLoaded) {
10441             ClearHighlights();
10442             flipView = WhiteOnMove(currentMove);
10443             if (moveType == GameUnfinished) flipView = !flipView;
10444             if (appData.debugMode)
10445               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10446         }
10447         break;
10448
10449       case EndOfFile:
10450         if (appData.debugMode)
10451           fprintf(debugFP, "Parser hit end of file\n");
10452         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10453           case MT_NONE:
10454           case MT_CHECK:
10455             break;
10456           case MT_CHECKMATE:
10457           case MT_STAINMATE:
10458             if (WhiteOnMove(currentMove)) {
10459                 GameEnds(BlackWins, "Black mates", GE_FILE);
10460             } else {
10461                 GameEnds(WhiteWins, "White mates", GE_FILE);
10462             }
10463             break;
10464           case MT_STALEMATE:
10465             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10466             break;
10467         }
10468         done = TRUE;
10469         break;
10470
10471       case MoveNumberOne:
10472         if (lastLoadGameStart == GNUChessGame) {
10473             /* GNUChessGames have numbers, but they aren't move numbers */
10474             if (appData.debugMode)
10475               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10476                       yy_text, (int) moveType);
10477             return LoadGameOneMove(EndOfFile); /* tail recursion */
10478         }
10479         /* else fall thru */
10480
10481       case XBoardGame:
10482       case GNUChessGame:
10483       case PGNTag:
10484         /* Reached start of next game in file */
10485         if (appData.debugMode)
10486           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10487         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10488           case MT_NONE:
10489           case MT_CHECK:
10490             break;
10491           case MT_CHECKMATE:
10492           case MT_STAINMATE:
10493             if (WhiteOnMove(currentMove)) {
10494                 GameEnds(BlackWins, "Black mates", GE_FILE);
10495             } else {
10496                 GameEnds(WhiteWins, "White mates", GE_FILE);
10497             }
10498             break;
10499           case MT_STALEMATE:
10500             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10501             break;
10502         }
10503         done = TRUE;
10504         break;
10505
10506       case PositionDiagram:     /* should not happen; ignore */
10507       case ElapsedTime:         /* ignore */
10508       case NAG:                 /* ignore */
10509         if (appData.debugMode)
10510           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10511                   yy_text, (int) moveType);
10512         return LoadGameOneMove(EndOfFile); /* tail recursion */
10513
10514       case IllegalMove:
10515         if (appData.testLegality) {
10516             if (appData.debugMode)
10517               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10518             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10519                     (forwardMostMove / 2) + 1,
10520                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10521             DisplayError(move, 0);
10522             done = TRUE;
10523         } else {
10524             if (appData.debugMode)
10525               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10526                       yy_text, currentMoveString);
10527             fromX = currentMoveString[0] - AAA;
10528             fromY = currentMoveString[1] - ONE;
10529             toX = currentMoveString[2] - AAA;
10530             toY = currentMoveString[3] - ONE;
10531             promoChar = currentMoveString[4];
10532         }
10533         break;
10534
10535       case AmbiguousMove:
10536         if (appData.debugMode)
10537           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10538         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10539                 (forwardMostMove / 2) + 1,
10540                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10541         DisplayError(move, 0);
10542         done = TRUE;
10543         break;
10544
10545       default:
10546       case ImpossibleMove:
10547         if (appData.debugMode)
10548           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10549         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10550                 (forwardMostMove / 2) + 1,
10551                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10552         DisplayError(move, 0);
10553         done = TRUE;
10554         break;
10555     }
10556
10557     if (done) {
10558         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10559             DrawPosition(FALSE, boards[currentMove]);
10560             DisplayBothClocks();
10561             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10562               DisplayComment(currentMove - 1, commentList[currentMove]);
10563         }
10564         (void) StopLoadGameTimer();
10565         gameFileFP = NULL;
10566         cmailOldMove = forwardMostMove;
10567         return FALSE;
10568     } else {
10569         /* currentMoveString is set as a side-effect of yylex */
10570
10571         thinkOutput[0] = NULLCHAR;
10572         MakeMove(fromX, fromY, toX, toY, promoChar);
10573         currentMove = forwardMostMove;
10574         return TRUE;
10575     }
10576 }
10577
10578 /* Load the nth game from the given file */
10579 int
10580 LoadGameFromFile(filename, n, title, useList)
10581      char *filename;
10582      int n;
10583      char *title;
10584      /*Boolean*/ int useList;
10585 {
10586     FILE *f;
10587     char buf[MSG_SIZ];
10588
10589     if (strcmp(filename, "-") == 0) {
10590         f = stdin;
10591         title = "stdin";
10592     } else {
10593         f = fopen(filename, "rb");
10594         if (f == NULL) {
10595           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10596             DisplayError(buf, errno);
10597             return FALSE;
10598         }
10599     }
10600     if (fseek(f, 0, 0) == -1) {
10601         /* f is not seekable; probably a pipe */
10602         useList = FALSE;
10603     }
10604     if (useList && n == 0) {
10605         int error = GameListBuild(f);
10606         if (error) {
10607             DisplayError(_("Cannot build game list"), error);
10608         } else if (!ListEmpty(&gameList) &&
10609                    ((ListGame *) gameList.tailPred)->number > 1) {
10610             GameListPopUp(f, title);
10611             return TRUE;
10612         }
10613         GameListDestroy();
10614         n = 1;
10615     }
10616     if (n == 0) n = 1;
10617     return LoadGame(f, n, title, FALSE);
10618 }
10619
10620
10621 void
10622 MakeRegisteredMove()
10623 {
10624     int fromX, fromY, toX, toY;
10625     char promoChar;
10626     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10627         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10628           case CMAIL_MOVE:
10629           case CMAIL_DRAW:
10630             if (appData.debugMode)
10631               fprintf(debugFP, "Restoring %s for game %d\n",
10632                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10633
10634             thinkOutput[0] = NULLCHAR;
10635             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10636             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10637             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10638             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10639             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10640             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10641             MakeMove(fromX, fromY, toX, toY, promoChar);
10642             ShowMove(fromX, fromY, toX, toY);
10643
10644             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10645               case MT_NONE:
10646               case MT_CHECK:
10647                 break;
10648
10649               case MT_CHECKMATE:
10650               case MT_STAINMATE:
10651                 if (WhiteOnMove(currentMove)) {
10652                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10653                 } else {
10654                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10655                 }
10656                 break;
10657
10658               case MT_STALEMATE:
10659                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10660                 break;
10661             }
10662
10663             break;
10664
10665           case CMAIL_RESIGN:
10666             if (WhiteOnMove(currentMove)) {
10667                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10668             } else {
10669                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10670             }
10671             break;
10672
10673           case CMAIL_ACCEPT:
10674             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10675             break;
10676
10677           default:
10678             break;
10679         }
10680     }
10681
10682     return;
10683 }
10684
10685 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10686 int
10687 CmailLoadGame(f, gameNumber, title, useList)
10688      FILE *f;
10689      int gameNumber;
10690      char *title;
10691      int useList;
10692 {
10693     int retVal;
10694
10695     if (gameNumber > nCmailGames) {
10696         DisplayError(_("No more games in this message"), 0);
10697         return FALSE;
10698     }
10699     if (f == lastLoadGameFP) {
10700         int offset = gameNumber - lastLoadGameNumber;
10701         if (offset == 0) {
10702             cmailMsg[0] = NULLCHAR;
10703             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10704                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10705                 nCmailMovesRegistered--;
10706             }
10707             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10708             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10709                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10710             }
10711         } else {
10712             if (! RegisterMove()) return FALSE;
10713         }
10714     }
10715
10716     retVal = LoadGame(f, gameNumber, title, useList);
10717
10718     /* Make move registered during previous look at this game, if any */
10719     MakeRegisteredMove();
10720
10721     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10722         commentList[currentMove]
10723           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10724         DisplayComment(currentMove - 1, commentList[currentMove]);
10725     }
10726
10727     return retVal;
10728 }
10729
10730 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10731 int
10732 ReloadGame(offset)
10733      int offset;
10734 {
10735     int gameNumber = lastLoadGameNumber + offset;
10736     if (lastLoadGameFP == NULL) {
10737         DisplayError(_("No game has been loaded yet"), 0);
10738         return FALSE;
10739     }
10740     if (gameNumber <= 0) {
10741         DisplayError(_("Can't back up any further"), 0);
10742         return FALSE;
10743     }
10744     if (cmailMsgLoaded) {
10745         return CmailLoadGame(lastLoadGameFP, gameNumber,
10746                              lastLoadGameTitle, lastLoadGameUseList);
10747     } else {
10748         return LoadGame(lastLoadGameFP, gameNumber,
10749                         lastLoadGameTitle, lastLoadGameUseList);
10750     }
10751 }
10752
10753
10754
10755 /* Load the nth game from open file f */
10756 int
10757 LoadGame(f, gameNumber, title, useList)
10758      FILE *f;
10759      int gameNumber;
10760      char *title;
10761      int useList;
10762 {
10763     ChessMove cm;
10764     char buf[MSG_SIZ];
10765     int gn = gameNumber;
10766     ListGame *lg = NULL;
10767     int numPGNTags = 0;
10768     int err;
10769     GameMode oldGameMode;
10770     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10771
10772     if (appData.debugMode)
10773         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10774
10775     if (gameMode == Training )
10776         SetTrainingModeOff();
10777
10778     oldGameMode = gameMode;
10779     if (gameMode != BeginningOfGame) {
10780       Reset(FALSE, TRUE);
10781     }
10782
10783     gameFileFP = f;
10784     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10785         fclose(lastLoadGameFP);
10786     }
10787
10788     if (useList) {
10789         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10790
10791         if (lg) {
10792             fseek(f, lg->offset, 0);
10793             GameListHighlight(gameNumber);
10794             gn = 1;
10795         }
10796         else {
10797             DisplayError(_("Game number out of range"), 0);
10798             return FALSE;
10799         }
10800     } else {
10801         GameListDestroy();
10802         if (fseek(f, 0, 0) == -1) {
10803             if (f == lastLoadGameFP ?
10804                 gameNumber == lastLoadGameNumber + 1 :
10805                 gameNumber == 1) {
10806                 gn = 1;
10807             } else {
10808                 DisplayError(_("Can't seek on game file"), 0);
10809                 return FALSE;
10810             }
10811         }
10812     }
10813     lastLoadGameFP = f;
10814     lastLoadGameNumber = gameNumber;
10815     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10816     lastLoadGameUseList = useList;
10817
10818     yynewfile(f);
10819
10820     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10821       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10822                 lg->gameInfo.black);
10823             DisplayTitle(buf);
10824     } else if (*title != NULLCHAR) {
10825         if (gameNumber > 1) {
10826           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10827             DisplayTitle(buf);
10828         } else {
10829             DisplayTitle(title);
10830         }
10831     }
10832
10833     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10834         gameMode = PlayFromGameFile;
10835         ModeHighlight();
10836     }
10837
10838     currentMove = forwardMostMove = backwardMostMove = 0;
10839     CopyBoard(boards[0], initialPosition);
10840     StopClocks();
10841
10842     /*
10843      * Skip the first gn-1 games in the file.
10844      * Also skip over anything that precedes an identifiable
10845      * start of game marker, to avoid being confused by
10846      * garbage at the start of the file.  Currently
10847      * recognized start of game markers are the move number "1",
10848      * the pattern "gnuchess .* game", the pattern
10849      * "^[#;%] [^ ]* game file", and a PGN tag block.
10850      * A game that starts with one of the latter two patterns
10851      * will also have a move number 1, possibly
10852      * following a position diagram.
10853      * 5-4-02: Let's try being more lenient and allowing a game to
10854      * start with an unnumbered move.  Does that break anything?
10855      */
10856     cm = lastLoadGameStart = EndOfFile;
10857     while (gn > 0) {
10858         yyboardindex = forwardMostMove;
10859         cm = (ChessMove) Myylex();
10860         switch (cm) {
10861           case EndOfFile:
10862             if (cmailMsgLoaded) {
10863                 nCmailGames = CMAIL_MAX_GAMES - gn;
10864             } else {
10865                 Reset(TRUE, TRUE);
10866                 DisplayError(_("Game not found in file"), 0);
10867             }
10868             return FALSE;
10869
10870           case GNUChessGame:
10871           case XBoardGame:
10872             gn--;
10873             lastLoadGameStart = cm;
10874             break;
10875
10876           case MoveNumberOne:
10877             switch (lastLoadGameStart) {
10878               case GNUChessGame:
10879               case XBoardGame:
10880               case PGNTag:
10881                 break;
10882               case MoveNumberOne:
10883               case EndOfFile:
10884                 gn--;           /* count this game */
10885                 lastLoadGameStart = cm;
10886                 break;
10887               default:
10888                 /* impossible */
10889                 break;
10890             }
10891             break;
10892
10893           case PGNTag:
10894             switch (lastLoadGameStart) {
10895               case GNUChessGame:
10896               case PGNTag:
10897               case MoveNumberOne:
10898               case EndOfFile:
10899                 gn--;           /* count this game */
10900                 lastLoadGameStart = cm;
10901                 break;
10902               case XBoardGame:
10903                 lastLoadGameStart = cm; /* game counted already */
10904                 break;
10905               default:
10906                 /* impossible */
10907                 break;
10908             }
10909             if (gn > 0) {
10910                 do {
10911                     yyboardindex = forwardMostMove;
10912                     cm = (ChessMove) Myylex();
10913                 } while (cm == PGNTag || cm == Comment);
10914             }
10915             break;
10916
10917           case WhiteWins:
10918           case BlackWins:
10919           case GameIsDrawn:
10920             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10921                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10922                     != CMAIL_OLD_RESULT) {
10923                     nCmailResults ++ ;
10924                     cmailResult[  CMAIL_MAX_GAMES
10925                                 - gn - 1] = CMAIL_OLD_RESULT;
10926                 }
10927             }
10928             break;
10929
10930           case NormalMove:
10931             /* Only a NormalMove can be at the start of a game
10932              * without a position diagram. */
10933             if (lastLoadGameStart == EndOfFile ) {
10934               gn--;
10935               lastLoadGameStart = MoveNumberOne;
10936             }
10937             break;
10938
10939           default:
10940             break;
10941         }
10942     }
10943
10944     if (appData.debugMode)
10945       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10946
10947     if (cm == XBoardGame) {
10948         /* Skip any header junk before position diagram and/or move 1 */
10949         for (;;) {
10950             yyboardindex = forwardMostMove;
10951             cm = (ChessMove) Myylex();
10952
10953             if (cm == EndOfFile ||
10954                 cm == GNUChessGame || cm == XBoardGame) {
10955                 /* Empty game; pretend end-of-file and handle later */
10956                 cm = EndOfFile;
10957                 break;
10958             }
10959
10960             if (cm == MoveNumberOne || cm == PositionDiagram ||
10961                 cm == PGNTag || cm == Comment)
10962               break;
10963         }
10964     } else if (cm == GNUChessGame) {
10965         if (gameInfo.event != NULL) {
10966             free(gameInfo.event);
10967         }
10968         gameInfo.event = StrSave(yy_text);
10969     }
10970
10971     startedFromSetupPosition = FALSE;
10972     while (cm == PGNTag) {
10973         if (appData.debugMode)
10974           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10975         err = ParsePGNTag(yy_text, &gameInfo);
10976         if (!err) numPGNTags++;
10977
10978         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10979         if(gameInfo.variant != oldVariant) {
10980             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10981             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10982             InitPosition(TRUE);
10983             oldVariant = gameInfo.variant;
10984             if (appData.debugMode)
10985               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10986         }
10987
10988
10989         if (gameInfo.fen != NULL) {
10990           Board initial_position;
10991           startedFromSetupPosition = TRUE;
10992           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10993             Reset(TRUE, TRUE);
10994             DisplayError(_("Bad FEN position in file"), 0);
10995             return FALSE;
10996           }
10997           CopyBoard(boards[0], initial_position);
10998           if (blackPlaysFirst) {
10999             currentMove = forwardMostMove = backwardMostMove = 1;
11000             CopyBoard(boards[1], initial_position);
11001             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11002             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11003             timeRemaining[0][1] = whiteTimeRemaining;
11004             timeRemaining[1][1] = blackTimeRemaining;
11005             if (commentList[0] != NULL) {
11006               commentList[1] = commentList[0];
11007               commentList[0] = NULL;
11008             }
11009           } else {
11010             currentMove = forwardMostMove = backwardMostMove = 0;
11011           }
11012           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11013           {   int i;
11014               initialRulePlies = FENrulePlies;
11015               for( i=0; i< nrCastlingRights; i++ )
11016                   initialRights[i] = initial_position[CASTLING][i];
11017           }
11018           yyboardindex = forwardMostMove;
11019           free(gameInfo.fen);
11020           gameInfo.fen = NULL;
11021         }
11022
11023         yyboardindex = forwardMostMove;
11024         cm = (ChessMove) Myylex();
11025
11026         /* Handle comments interspersed among the tags */
11027         while (cm == Comment) {
11028             char *p;
11029             if (appData.debugMode)
11030               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11031             p = yy_text;
11032             AppendComment(currentMove, p, FALSE);
11033             yyboardindex = forwardMostMove;
11034             cm = (ChessMove) Myylex();
11035         }
11036     }
11037
11038     /* don't rely on existence of Event tag since if game was
11039      * pasted from clipboard the Event tag may not exist
11040      */
11041     if (numPGNTags > 0){
11042         char *tags;
11043         if (gameInfo.variant == VariantNormal) {
11044           VariantClass v = StringToVariant(gameInfo.event);
11045           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11046           if(v < VariantShogi) gameInfo.variant = v;
11047         }
11048         if (!matchMode) {
11049           if( appData.autoDisplayTags ) {
11050             tags = PGNTags(&gameInfo);
11051             TagsPopUp(tags, CmailMsg());
11052             free(tags);
11053           }
11054         }
11055     } else {
11056         /* Make something up, but don't display it now */
11057         SetGameInfo();
11058         TagsPopDown();
11059     }
11060
11061     if (cm == PositionDiagram) {
11062         int i, j;
11063         char *p;
11064         Board initial_position;
11065
11066         if (appData.debugMode)
11067           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11068
11069         if (!startedFromSetupPosition) {
11070             p = yy_text;
11071             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11072               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11073                 switch (*p) {
11074                   case '{':
11075                   case '[':
11076                   case '-':
11077                   case ' ':
11078                   case '\t':
11079                   case '\n':
11080                   case '\r':
11081                     break;
11082                   default:
11083                     initial_position[i][j++] = CharToPiece(*p);
11084                     break;
11085                 }
11086             while (*p == ' ' || *p == '\t' ||
11087                    *p == '\n' || *p == '\r') p++;
11088
11089             if (strncmp(p, "black", strlen("black"))==0)
11090               blackPlaysFirst = TRUE;
11091             else
11092               blackPlaysFirst = FALSE;
11093             startedFromSetupPosition = TRUE;
11094
11095             CopyBoard(boards[0], initial_position);
11096             if (blackPlaysFirst) {
11097                 currentMove = forwardMostMove = backwardMostMove = 1;
11098                 CopyBoard(boards[1], initial_position);
11099                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11100                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11101                 timeRemaining[0][1] = whiteTimeRemaining;
11102                 timeRemaining[1][1] = blackTimeRemaining;
11103                 if (commentList[0] != NULL) {
11104                     commentList[1] = commentList[0];
11105                     commentList[0] = NULL;
11106                 }
11107             } else {
11108                 currentMove = forwardMostMove = backwardMostMove = 0;
11109             }
11110         }
11111         yyboardindex = forwardMostMove;
11112         cm = (ChessMove) Myylex();
11113     }
11114
11115     if (first.pr == NoProc) {
11116         StartChessProgram(&first);
11117     }
11118     InitChessProgram(&first, FALSE);
11119     SendToProgram("force\n", &first);
11120     if (startedFromSetupPosition) {
11121         SendBoard(&first, forwardMostMove);
11122     if (appData.debugMode) {
11123         fprintf(debugFP, "Load Game\n");
11124     }
11125         DisplayBothClocks();
11126     }
11127
11128     /* [HGM] server: flag to write setup moves in broadcast file as one */
11129     loadFlag = appData.suppressLoadMoves;
11130
11131     while (cm == Comment) {
11132         char *p;
11133         if (appData.debugMode)
11134           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11135         p = yy_text;
11136         AppendComment(currentMove, p, FALSE);
11137         yyboardindex = forwardMostMove;
11138         cm = (ChessMove) Myylex();
11139     }
11140
11141     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11142         cm == WhiteWins || cm == BlackWins ||
11143         cm == GameIsDrawn || cm == GameUnfinished) {
11144         DisplayMessage("", _("No moves in game"));
11145         if (cmailMsgLoaded) {
11146             if (appData.debugMode)
11147               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11148             ClearHighlights();
11149             flipView = FALSE;
11150         }
11151         DrawPosition(FALSE, boards[currentMove]);
11152         DisplayBothClocks();
11153         gameMode = EditGame;
11154         ModeHighlight();
11155         gameFileFP = NULL;
11156         cmailOldMove = 0;
11157         return TRUE;
11158     }
11159
11160     // [HGM] PV info: routine tests if comment empty
11161     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11162         DisplayComment(currentMove - 1, commentList[currentMove]);
11163     }
11164     if (!matchMode && appData.timeDelay != 0)
11165       DrawPosition(FALSE, boards[currentMove]);
11166
11167     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11168       programStats.ok_to_send = 1;
11169     }
11170
11171     /* if the first token after the PGN tags is a move
11172      * and not move number 1, retrieve it from the parser
11173      */
11174     if (cm != MoveNumberOne)
11175         LoadGameOneMove(cm);
11176
11177     /* load the remaining moves from the file */
11178     while (LoadGameOneMove(EndOfFile)) {
11179       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11180       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11181     }
11182
11183     /* rewind to the start of the game */
11184     currentMove = backwardMostMove;
11185
11186     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11187
11188     if (oldGameMode == AnalyzeFile ||
11189         oldGameMode == AnalyzeMode) {
11190       AnalyzeFileEvent();
11191     }
11192
11193     if (matchMode || appData.timeDelay == 0) {
11194       ToEndEvent();
11195       gameMode = EditGame;
11196       ModeHighlight();
11197     } else if (appData.timeDelay > 0) {
11198       AutoPlayGameLoop();
11199     }
11200
11201     if (appData.debugMode)
11202         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11203
11204     loadFlag = 0; /* [HGM] true game starts */
11205     return TRUE;
11206 }
11207
11208 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11209 int
11210 ReloadPosition(offset)
11211      int offset;
11212 {
11213     int positionNumber = lastLoadPositionNumber + offset;
11214     if (lastLoadPositionFP == NULL) {
11215         DisplayError(_("No position has been loaded yet"), 0);
11216         return FALSE;
11217     }
11218     if (positionNumber <= 0) {
11219         DisplayError(_("Can't back up any further"), 0);
11220         return FALSE;
11221     }
11222     return LoadPosition(lastLoadPositionFP, positionNumber,
11223                         lastLoadPositionTitle);
11224 }
11225
11226 /* Load the nth position from the given file */
11227 int
11228 LoadPositionFromFile(filename, n, title)
11229      char *filename;
11230      int n;
11231      char *title;
11232 {
11233     FILE *f;
11234     char buf[MSG_SIZ];
11235
11236     if (strcmp(filename, "-") == 0) {
11237         return LoadPosition(stdin, n, "stdin");
11238     } else {
11239         f = fopen(filename, "rb");
11240         if (f == NULL) {
11241             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11242             DisplayError(buf, errno);
11243             return FALSE;
11244         } else {
11245             return LoadPosition(f, n, title);
11246         }
11247     }
11248 }
11249
11250 /* Load the nth position from the given open file, and close it */
11251 int
11252 LoadPosition(f, positionNumber, title)
11253      FILE *f;
11254      int positionNumber;
11255      char *title;
11256 {
11257     char *p, line[MSG_SIZ];
11258     Board initial_position;
11259     int i, j, fenMode, pn;
11260
11261     if (gameMode == Training )
11262         SetTrainingModeOff();
11263
11264     if (gameMode != BeginningOfGame) {
11265         Reset(FALSE, TRUE);
11266     }
11267     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11268         fclose(lastLoadPositionFP);
11269     }
11270     if (positionNumber == 0) positionNumber = 1;
11271     lastLoadPositionFP = f;
11272     lastLoadPositionNumber = positionNumber;
11273     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11274     if (first.pr == NoProc) {
11275       StartChessProgram(&first);
11276       InitChessProgram(&first, FALSE);
11277     }
11278     pn = positionNumber;
11279     if (positionNumber < 0) {
11280         /* Negative position number means to seek to that byte offset */
11281         if (fseek(f, -positionNumber, 0) == -1) {
11282             DisplayError(_("Can't seek on position file"), 0);
11283             return FALSE;
11284         };
11285         pn = 1;
11286     } else {
11287         if (fseek(f, 0, 0) == -1) {
11288             if (f == lastLoadPositionFP ?
11289                 positionNumber == lastLoadPositionNumber + 1 :
11290                 positionNumber == 1) {
11291                 pn = 1;
11292             } else {
11293                 DisplayError(_("Can't seek on position file"), 0);
11294                 return FALSE;
11295             }
11296         }
11297     }
11298     /* See if this file is FEN or old-style xboard */
11299     if (fgets(line, MSG_SIZ, f) == NULL) {
11300         DisplayError(_("Position not found in file"), 0);
11301         return FALSE;
11302     }
11303     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11304     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11305
11306     if (pn >= 2) {
11307         if (fenMode || line[0] == '#') pn--;
11308         while (pn > 0) {
11309             /* skip positions before number pn */
11310             if (fgets(line, MSG_SIZ, f) == NULL) {
11311                 Reset(TRUE, TRUE);
11312                 DisplayError(_("Position not found in file"), 0);
11313                 return FALSE;
11314             }
11315             if (fenMode || line[0] == '#') pn--;
11316         }
11317     }
11318
11319     if (fenMode) {
11320         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11321             DisplayError(_("Bad FEN position in file"), 0);
11322             return FALSE;
11323         }
11324     } else {
11325         (void) fgets(line, MSG_SIZ, f);
11326         (void) fgets(line, MSG_SIZ, f);
11327
11328         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11329             (void) fgets(line, MSG_SIZ, f);
11330             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11331                 if (*p == ' ')
11332                   continue;
11333                 initial_position[i][j++] = CharToPiece(*p);
11334             }
11335         }
11336
11337         blackPlaysFirst = FALSE;
11338         if (!feof(f)) {
11339             (void) fgets(line, MSG_SIZ, f);
11340             if (strncmp(line, "black", strlen("black"))==0)
11341               blackPlaysFirst = TRUE;
11342         }
11343     }
11344     startedFromSetupPosition = TRUE;
11345
11346     SendToProgram("force\n", &first);
11347     CopyBoard(boards[0], initial_position);
11348     if (blackPlaysFirst) {
11349         currentMove = forwardMostMove = backwardMostMove = 1;
11350         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11351         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11352         CopyBoard(boards[1], initial_position);
11353         DisplayMessage("", _("Black to play"));
11354     } else {
11355         currentMove = forwardMostMove = backwardMostMove = 0;
11356         DisplayMessage("", _("White to play"));
11357     }
11358     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11359     SendBoard(&first, forwardMostMove);
11360     if (appData.debugMode) {
11361 int i, j;
11362   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11363   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11364         fprintf(debugFP, "Load Position\n");
11365     }
11366
11367     if (positionNumber > 1) {
11368       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11369         DisplayTitle(line);
11370     } else {
11371         DisplayTitle(title);
11372     }
11373     gameMode = EditGame;
11374     ModeHighlight();
11375     ResetClocks();
11376     timeRemaining[0][1] = whiteTimeRemaining;
11377     timeRemaining[1][1] = blackTimeRemaining;
11378     DrawPosition(FALSE, boards[currentMove]);
11379
11380     return TRUE;
11381 }
11382
11383
11384 void
11385 CopyPlayerNameIntoFileName(dest, src)
11386      char **dest, *src;
11387 {
11388     while (*src != NULLCHAR && *src != ',') {
11389         if (*src == ' ') {
11390             *(*dest)++ = '_';
11391             src++;
11392         } else {
11393             *(*dest)++ = *src++;
11394         }
11395     }
11396 }
11397
11398 char *DefaultFileName(ext)
11399      char *ext;
11400 {
11401     static char def[MSG_SIZ];
11402     char *p;
11403
11404     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11405         p = def;
11406         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11407         *p++ = '-';
11408         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11409         *p++ = '.';
11410         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11411     } else {
11412         def[0] = NULLCHAR;
11413     }
11414     return def;
11415 }
11416
11417 /* Save the current game to the given file */
11418 int
11419 SaveGameToFile(filename, append)
11420      char *filename;
11421      int append;
11422 {
11423     FILE *f;
11424     char buf[MSG_SIZ];
11425     int result;
11426
11427     if (strcmp(filename, "-") == 0) {
11428         return SaveGame(stdout, 0, NULL);
11429     } else {
11430         f = fopen(filename, append ? "a" : "w");
11431         if (f == NULL) {
11432             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11433             DisplayError(buf, errno);
11434             return FALSE;
11435         } else {
11436             safeStrCpy(buf, lastMsg, MSG_SIZ);
11437             DisplayMessage(_("Waiting for access to save file"), "");
11438             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11439             DisplayMessage(_("Saving game"), "");
11440             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11441             result = SaveGame(f, 0, NULL);
11442             DisplayMessage(buf, "");
11443             return result;
11444         }
11445     }
11446 }
11447
11448 char *
11449 SavePart(str)
11450      char *str;
11451 {
11452     static char buf[MSG_SIZ];
11453     char *p;
11454
11455     p = strchr(str, ' ');
11456     if (p == NULL) return str;
11457     strncpy(buf, str, p - str);
11458     buf[p - str] = NULLCHAR;
11459     return buf;
11460 }
11461
11462 #define PGN_MAX_LINE 75
11463
11464 #define PGN_SIDE_WHITE  0
11465 #define PGN_SIDE_BLACK  1
11466
11467 /* [AS] */
11468 static int FindFirstMoveOutOfBook( int side )
11469 {
11470     int result = -1;
11471
11472     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11473         int index = backwardMostMove;
11474         int has_book_hit = 0;
11475
11476         if( (index % 2) != side ) {
11477             index++;
11478         }
11479
11480         while( index < forwardMostMove ) {
11481             /* Check to see if engine is in book */
11482             int depth = pvInfoList[index].depth;
11483             int score = pvInfoList[index].score;
11484             int in_book = 0;
11485
11486             if( depth <= 2 ) {
11487                 in_book = 1;
11488             }
11489             else if( score == 0 && depth == 63 ) {
11490                 in_book = 1; /* Zappa */
11491             }
11492             else if( score == 2 && depth == 99 ) {
11493                 in_book = 1; /* Abrok */
11494             }
11495
11496             has_book_hit += in_book;
11497
11498             if( ! in_book ) {
11499                 result = index;
11500
11501                 break;
11502             }
11503
11504             index += 2;
11505         }
11506     }
11507
11508     return result;
11509 }
11510
11511 /* [AS] */
11512 void GetOutOfBookInfo( char * buf )
11513 {
11514     int oob[2];
11515     int i;
11516     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11517
11518     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11519     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11520
11521     *buf = '\0';
11522
11523     if( oob[0] >= 0 || oob[1] >= 0 ) {
11524         for( i=0; i<2; i++ ) {
11525             int idx = oob[i];
11526
11527             if( idx >= 0 ) {
11528                 if( i > 0 && oob[0] >= 0 ) {
11529                     strcat( buf, "   " );
11530                 }
11531
11532                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11533                 sprintf( buf+strlen(buf), "%s%.2f",
11534                     pvInfoList[idx].score >= 0 ? "+" : "",
11535                     pvInfoList[idx].score / 100.0 );
11536             }
11537         }
11538     }
11539 }
11540
11541 /* Save game in PGN style and close the file */
11542 int
11543 SaveGamePGN(f)
11544      FILE *f;
11545 {
11546     int i, offset, linelen, newblock;
11547     time_t tm;
11548 //    char *movetext;
11549     char numtext[32];
11550     int movelen, numlen, blank;
11551     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11552
11553     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11554
11555     tm = time((time_t *) NULL);
11556
11557     PrintPGNTags(f, &gameInfo);
11558
11559     if (backwardMostMove > 0 || startedFromSetupPosition) {
11560         char *fen = PositionToFEN(backwardMostMove, NULL);
11561         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11562         fprintf(f, "\n{--------------\n");
11563         PrintPosition(f, backwardMostMove);
11564         fprintf(f, "--------------}\n");
11565         free(fen);
11566     }
11567     else {
11568         /* [AS] Out of book annotation */
11569         if( appData.saveOutOfBookInfo ) {
11570             char buf[64];
11571
11572             GetOutOfBookInfo( buf );
11573
11574             if( buf[0] != '\0' ) {
11575                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11576             }
11577         }
11578
11579         fprintf(f, "\n");
11580     }
11581
11582     i = backwardMostMove;
11583     linelen = 0;
11584     newblock = TRUE;
11585
11586     while (i < forwardMostMove) {
11587         /* Print comments preceding this move */
11588         if (commentList[i] != NULL) {
11589             if (linelen > 0) fprintf(f, "\n");
11590             fprintf(f, "%s", commentList[i]);
11591             linelen = 0;
11592             newblock = TRUE;
11593         }
11594
11595         /* Format move number */
11596         if ((i % 2) == 0)
11597           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11598         else
11599           if (newblock)
11600             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11601           else
11602             numtext[0] = NULLCHAR;
11603
11604         numlen = strlen(numtext);
11605         newblock = FALSE;
11606
11607         /* Print move number */
11608         blank = linelen > 0 && numlen > 0;
11609         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11610             fprintf(f, "\n");
11611             linelen = 0;
11612             blank = 0;
11613         }
11614         if (blank) {
11615             fprintf(f, " ");
11616             linelen++;
11617         }
11618         fprintf(f, "%s", numtext);
11619         linelen += numlen;
11620
11621         /* Get move */
11622         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11623         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11624
11625         /* Print move */
11626         blank = linelen > 0 && movelen > 0;
11627         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11628             fprintf(f, "\n");
11629             linelen = 0;
11630             blank = 0;
11631         }
11632         if (blank) {
11633             fprintf(f, " ");
11634             linelen++;
11635         }
11636         fprintf(f, "%s", move_buffer);
11637         linelen += movelen;
11638
11639         /* [AS] Add PV info if present */
11640         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11641             /* [HGM] add time */
11642             char buf[MSG_SIZ]; int seconds;
11643
11644             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11645
11646             if( seconds <= 0)
11647               buf[0] = 0;
11648             else
11649               if( seconds < 30 )
11650                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11651               else
11652                 {
11653                   seconds = (seconds + 4)/10; // round to full seconds
11654                   if( seconds < 60 )
11655                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11656                   else
11657                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11658                 }
11659
11660             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11661                       pvInfoList[i].score >= 0 ? "+" : "",
11662                       pvInfoList[i].score / 100.0,
11663                       pvInfoList[i].depth,
11664                       buf );
11665
11666             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11667
11668             /* Print score/depth */
11669             blank = linelen > 0 && movelen > 0;
11670             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11671                 fprintf(f, "\n");
11672                 linelen = 0;
11673                 blank = 0;
11674             }
11675             if (blank) {
11676                 fprintf(f, " ");
11677                 linelen++;
11678             }
11679             fprintf(f, "%s", move_buffer);
11680             linelen += movelen;
11681         }
11682
11683         i++;
11684     }
11685
11686     /* Start a new line */
11687     if (linelen > 0) fprintf(f, "\n");
11688
11689     /* Print comments after last move */
11690     if (commentList[i] != NULL) {
11691         fprintf(f, "%s\n", commentList[i]);
11692     }
11693
11694     /* Print result */
11695     if (gameInfo.resultDetails != NULL &&
11696         gameInfo.resultDetails[0] != NULLCHAR) {
11697         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11698                 PGNResult(gameInfo.result));
11699     } else {
11700         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11701     }
11702
11703     fclose(f);
11704     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11705     return TRUE;
11706 }
11707
11708 /* Save game in old style and close the file */
11709 int
11710 SaveGameOldStyle(f)
11711      FILE *f;
11712 {
11713     int i, offset;
11714     time_t tm;
11715
11716     tm = time((time_t *) NULL);
11717
11718     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11719     PrintOpponents(f);
11720
11721     if (backwardMostMove > 0 || startedFromSetupPosition) {
11722         fprintf(f, "\n[--------------\n");
11723         PrintPosition(f, backwardMostMove);
11724         fprintf(f, "--------------]\n");
11725     } else {
11726         fprintf(f, "\n");
11727     }
11728
11729     i = backwardMostMove;
11730     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11731
11732     while (i < forwardMostMove) {
11733         if (commentList[i] != NULL) {
11734             fprintf(f, "[%s]\n", commentList[i]);
11735         }
11736
11737         if ((i % 2) == 1) {
11738             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11739             i++;
11740         } else {
11741             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11742             i++;
11743             if (commentList[i] != NULL) {
11744                 fprintf(f, "\n");
11745                 continue;
11746             }
11747             if (i >= forwardMostMove) {
11748                 fprintf(f, "\n");
11749                 break;
11750             }
11751             fprintf(f, "%s\n", parseList[i]);
11752             i++;
11753         }
11754     }
11755
11756     if (commentList[i] != NULL) {
11757         fprintf(f, "[%s]\n", commentList[i]);
11758     }
11759
11760     /* This isn't really the old style, but it's close enough */
11761     if (gameInfo.resultDetails != NULL &&
11762         gameInfo.resultDetails[0] != NULLCHAR) {
11763         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11764                 gameInfo.resultDetails);
11765     } else {
11766         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11767     }
11768
11769     fclose(f);
11770     return TRUE;
11771 }
11772
11773 /* Save the current game to open file f and close the file */
11774 int
11775 SaveGame(f, dummy, dummy2)
11776      FILE *f;
11777      int dummy;
11778      char *dummy2;
11779 {
11780     if (gameMode == EditPosition) EditPositionDone(TRUE);
11781     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11782     if (appData.oldSaveStyle)
11783       return SaveGameOldStyle(f);
11784     else
11785       return SaveGamePGN(f);
11786 }
11787
11788 /* Save the current position to the given file */
11789 int
11790 SavePositionToFile(filename)
11791      char *filename;
11792 {
11793     FILE *f;
11794     char buf[MSG_SIZ];
11795
11796     if (strcmp(filename, "-") == 0) {
11797         return SavePosition(stdout, 0, NULL);
11798     } else {
11799         f = fopen(filename, "a");
11800         if (f == NULL) {
11801             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11802             DisplayError(buf, errno);
11803             return FALSE;
11804         } else {
11805             safeStrCpy(buf, lastMsg, MSG_SIZ);
11806             DisplayMessage(_("Waiting for access to save file"), "");
11807             flock(fileno(f), LOCK_EX); // [HGM] lock
11808             DisplayMessage(_("Saving position"), "");
11809             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11810             SavePosition(f, 0, NULL);
11811             DisplayMessage(buf, "");
11812             return TRUE;
11813         }
11814     }
11815 }
11816
11817 /* Save the current position to the given open file and close the file */
11818 int
11819 SavePosition(f, dummy, dummy2)
11820      FILE *f;
11821      int dummy;
11822      char *dummy2;
11823 {
11824     time_t tm;
11825     char *fen;
11826
11827     if (gameMode == EditPosition) EditPositionDone(TRUE);
11828     if (appData.oldSaveStyle) {
11829         tm = time((time_t *) NULL);
11830
11831         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11832         PrintOpponents(f);
11833         fprintf(f, "[--------------\n");
11834         PrintPosition(f, currentMove);
11835         fprintf(f, "--------------]\n");
11836     } else {
11837         fen = PositionToFEN(currentMove, NULL);
11838         fprintf(f, "%s\n", fen);
11839         free(fen);
11840     }
11841     fclose(f);
11842     return TRUE;
11843 }
11844
11845 void
11846 ReloadCmailMsgEvent(unregister)
11847      int unregister;
11848 {
11849 #if !WIN32
11850     static char *inFilename = NULL;
11851     static char *outFilename;
11852     int i;
11853     struct stat inbuf, outbuf;
11854     int status;
11855
11856     /* Any registered moves are unregistered if unregister is set, */
11857     /* i.e. invoked by the signal handler */
11858     if (unregister) {
11859         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11860             cmailMoveRegistered[i] = FALSE;
11861             if (cmailCommentList[i] != NULL) {
11862                 free(cmailCommentList[i]);
11863                 cmailCommentList[i] = NULL;
11864             }
11865         }
11866         nCmailMovesRegistered = 0;
11867     }
11868
11869     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11870         cmailResult[i] = CMAIL_NOT_RESULT;
11871     }
11872     nCmailResults = 0;
11873
11874     if (inFilename == NULL) {
11875         /* Because the filenames are static they only get malloced once  */
11876         /* and they never get freed                                      */
11877         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11878         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11879
11880         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11881         sprintf(outFilename, "%s.out", appData.cmailGameName);
11882     }
11883
11884     status = stat(outFilename, &outbuf);
11885     if (status < 0) {
11886         cmailMailedMove = FALSE;
11887     } else {
11888         status = stat(inFilename, &inbuf);
11889         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11890     }
11891
11892     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11893        counts the games, notes how each one terminated, etc.
11894
11895        It would be nice to remove this kludge and instead gather all
11896        the information while building the game list.  (And to keep it
11897        in the game list nodes instead of having a bunch of fixed-size
11898        parallel arrays.)  Note this will require getting each game's
11899        termination from the PGN tags, as the game list builder does
11900        not process the game moves.  --mann
11901        */
11902     cmailMsgLoaded = TRUE;
11903     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11904
11905     /* Load first game in the file or popup game menu */
11906     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11907
11908 #endif /* !WIN32 */
11909     return;
11910 }
11911
11912 int
11913 RegisterMove()
11914 {
11915     FILE *f;
11916     char string[MSG_SIZ];
11917
11918     if (   cmailMailedMove
11919         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11920         return TRUE;            /* Allow free viewing  */
11921     }
11922
11923     /* Unregister move to ensure that we don't leave RegisterMove        */
11924     /* with the move registered when the conditions for registering no   */
11925     /* longer hold                                                       */
11926     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11927         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11928         nCmailMovesRegistered --;
11929
11930         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11931           {
11932               free(cmailCommentList[lastLoadGameNumber - 1]);
11933               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11934           }
11935     }
11936
11937     if (cmailOldMove == -1) {
11938         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11939         return FALSE;
11940     }
11941
11942     if (currentMove > cmailOldMove + 1) {
11943         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11944         return FALSE;
11945     }
11946
11947     if (currentMove < cmailOldMove) {
11948         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11949         return FALSE;
11950     }
11951
11952     if (forwardMostMove > currentMove) {
11953         /* Silently truncate extra moves */
11954         TruncateGame();
11955     }
11956
11957     if (   (currentMove == cmailOldMove + 1)
11958         || (   (currentMove == cmailOldMove)
11959             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11960                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11961         if (gameInfo.result != GameUnfinished) {
11962             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11963         }
11964
11965         if (commentList[currentMove] != NULL) {
11966             cmailCommentList[lastLoadGameNumber - 1]
11967               = StrSave(commentList[currentMove]);
11968         }
11969         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11970
11971         if (appData.debugMode)
11972           fprintf(debugFP, "Saving %s for game %d\n",
11973                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11974
11975         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11976
11977         f = fopen(string, "w");
11978         if (appData.oldSaveStyle) {
11979             SaveGameOldStyle(f); /* also closes the file */
11980
11981             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11982             f = fopen(string, "w");
11983             SavePosition(f, 0, NULL); /* also closes the file */
11984         } else {
11985             fprintf(f, "{--------------\n");
11986             PrintPosition(f, currentMove);
11987             fprintf(f, "--------------}\n\n");
11988
11989             SaveGame(f, 0, NULL); /* also closes the file*/
11990         }
11991
11992         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11993         nCmailMovesRegistered ++;
11994     } else if (nCmailGames == 1) {
11995         DisplayError(_("You have not made a move yet"), 0);
11996         return FALSE;
11997     }
11998
11999     return TRUE;
12000 }
12001
12002 void
12003 MailMoveEvent()
12004 {
12005 #if !WIN32
12006     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12007     FILE *commandOutput;
12008     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12009     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12010     int nBuffers;
12011     int i;
12012     int archived;
12013     char *arcDir;
12014
12015     if (! cmailMsgLoaded) {
12016         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12017         return;
12018     }
12019
12020     if (nCmailGames == nCmailResults) {
12021         DisplayError(_("No unfinished games"), 0);
12022         return;
12023     }
12024
12025 #if CMAIL_PROHIBIT_REMAIL
12026     if (cmailMailedMove) {
12027       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);
12028         DisplayError(msg, 0);
12029         return;
12030     }
12031 #endif
12032
12033     if (! (cmailMailedMove || RegisterMove())) return;
12034
12035     if (   cmailMailedMove
12036         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12037       snprintf(string, MSG_SIZ, partCommandString,
12038                appData.debugMode ? " -v" : "", appData.cmailGameName);
12039         commandOutput = popen(string, "r");
12040
12041         if (commandOutput == NULL) {
12042             DisplayError(_("Failed to invoke cmail"), 0);
12043         } else {
12044             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12045                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12046             }
12047             if (nBuffers > 1) {
12048                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12049                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12050                 nBytes = MSG_SIZ - 1;
12051             } else {
12052                 (void) memcpy(msg, buffer, nBytes);
12053             }
12054             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12055
12056             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12057                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12058
12059                 archived = TRUE;
12060                 for (i = 0; i < nCmailGames; i ++) {
12061                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12062                         archived = FALSE;
12063                     }
12064                 }
12065                 if (   archived
12066                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12067                         != NULL)) {
12068                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12069                            arcDir,
12070                            appData.cmailGameName,
12071                            gameInfo.date);
12072                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12073                     cmailMsgLoaded = FALSE;
12074                 }
12075             }
12076
12077             DisplayInformation(msg);
12078             pclose(commandOutput);
12079         }
12080     } else {
12081         if ((*cmailMsg) != '\0') {
12082             DisplayInformation(cmailMsg);
12083         }
12084     }
12085
12086     return;
12087 #endif /* !WIN32 */
12088 }
12089
12090 char *
12091 CmailMsg()
12092 {
12093 #if WIN32
12094     return NULL;
12095 #else
12096     int  prependComma = 0;
12097     char number[5];
12098     char string[MSG_SIZ];       /* Space for game-list */
12099     int  i;
12100
12101     if (!cmailMsgLoaded) return "";
12102
12103     if (cmailMailedMove) {
12104       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12105     } else {
12106         /* Create a list of games left */
12107       snprintf(string, MSG_SIZ, "[");
12108         for (i = 0; i < nCmailGames; i ++) {
12109             if (! (   cmailMoveRegistered[i]
12110                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12111                 if (prependComma) {
12112                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12113                 } else {
12114                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12115                     prependComma = 1;
12116                 }
12117
12118                 strcat(string, number);
12119             }
12120         }
12121         strcat(string, "]");
12122
12123         if (nCmailMovesRegistered + nCmailResults == 0) {
12124             switch (nCmailGames) {
12125               case 1:
12126                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12127                 break;
12128
12129               case 2:
12130                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12131                 break;
12132
12133               default:
12134                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12135                          nCmailGames);
12136                 break;
12137             }
12138         } else {
12139             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12140               case 1:
12141                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12142                          string);
12143                 break;
12144
12145               case 0:
12146                 if (nCmailResults == nCmailGames) {
12147                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12148                 } else {
12149                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12150                 }
12151                 break;
12152
12153               default:
12154                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12155                          string);
12156             }
12157         }
12158     }
12159     return cmailMsg;
12160 #endif /* WIN32 */
12161 }
12162
12163 void
12164 ResetGameEvent()
12165 {
12166     if (gameMode == Training)
12167       SetTrainingModeOff();
12168
12169     Reset(TRUE, TRUE);
12170     cmailMsgLoaded = FALSE;
12171     if (appData.icsActive) {
12172       SendToICS(ics_prefix);
12173       SendToICS("refresh\n");
12174     }
12175 }
12176
12177 void
12178 ExitEvent(status)
12179      int status;
12180 {
12181     exiting++;
12182     if (exiting > 2) {
12183       /* Give up on clean exit */
12184       exit(status);
12185     }
12186     if (exiting > 1) {
12187       /* Keep trying for clean exit */
12188       return;
12189     }
12190
12191     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12192
12193     if (telnetISR != NULL) {
12194       RemoveInputSource(telnetISR);
12195     }
12196     if (icsPR != NoProc) {
12197       DestroyChildProcess(icsPR, TRUE);
12198     }
12199
12200     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12201     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12202
12203     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12204     /* make sure this other one finishes before killing it!                  */
12205     if(endingGame) { int count = 0;
12206         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12207         while(endingGame && count++ < 10) DoSleep(1);
12208         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12209     }
12210
12211     /* Kill off chess programs */
12212     if (first.pr != NoProc) {
12213         ExitAnalyzeMode();
12214
12215         DoSleep( appData.delayBeforeQuit );
12216         SendToProgram("quit\n", &first);
12217         DoSleep( appData.delayAfterQuit );
12218         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12219     }
12220     if (second.pr != NoProc) {
12221         DoSleep( appData.delayBeforeQuit );
12222         SendToProgram("quit\n", &second);
12223         DoSleep( appData.delayAfterQuit );
12224         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12225     }
12226     if (first.isr != NULL) {
12227         RemoveInputSource(first.isr);
12228     }
12229     if (second.isr != NULL) {
12230         RemoveInputSource(second.isr);
12231     }
12232
12233     ShutDownFrontEnd();
12234     exit(status);
12235 }
12236
12237 void
12238 PauseEvent()
12239 {
12240     if (appData.debugMode)
12241         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12242     if (pausing) {
12243         pausing = FALSE;
12244         ModeHighlight();
12245         if (gameMode == MachinePlaysWhite ||
12246             gameMode == MachinePlaysBlack) {
12247             StartClocks();
12248         } else {
12249             DisplayBothClocks();
12250         }
12251         if (gameMode == PlayFromGameFile) {
12252             if (appData.timeDelay >= 0)
12253                 AutoPlayGameLoop();
12254         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12255             Reset(FALSE, TRUE);
12256             SendToICS(ics_prefix);
12257             SendToICS("refresh\n");
12258         } else if (currentMove < forwardMostMove) {
12259             ForwardInner(forwardMostMove);
12260         }
12261         pauseExamInvalid = FALSE;
12262     } else {
12263         switch (gameMode) {
12264           default:
12265             return;
12266           case IcsExamining:
12267             pauseExamForwardMostMove = forwardMostMove;
12268             pauseExamInvalid = FALSE;
12269             /* fall through */
12270           case IcsObserving:
12271           case IcsPlayingWhite:
12272           case IcsPlayingBlack:
12273             pausing = TRUE;
12274             ModeHighlight();
12275             return;
12276           case PlayFromGameFile:
12277             (void) StopLoadGameTimer();
12278             pausing = TRUE;
12279             ModeHighlight();
12280             break;
12281           case BeginningOfGame:
12282             if (appData.icsActive) return;
12283             /* else fall through */
12284           case MachinePlaysWhite:
12285           case MachinePlaysBlack:
12286           case TwoMachinesPlay:
12287             if (forwardMostMove == 0)
12288               return;           /* don't pause if no one has moved */
12289             if ((gameMode == MachinePlaysWhite &&
12290                  !WhiteOnMove(forwardMostMove)) ||
12291                 (gameMode == MachinePlaysBlack &&
12292                  WhiteOnMove(forwardMostMove))) {
12293                 StopClocks();
12294             }
12295             pausing = TRUE;
12296             ModeHighlight();
12297             break;
12298         }
12299     }
12300 }
12301
12302 void
12303 EditCommentEvent()
12304 {
12305     char title[MSG_SIZ];
12306
12307     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12308       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12309     } else {
12310       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12311                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12312                parseList[currentMove - 1]);
12313     }
12314
12315     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12316 }
12317
12318
12319 void
12320 EditTagsEvent()
12321 {
12322     char *tags = PGNTags(&gameInfo);
12323     bookUp = FALSE;
12324     EditTagsPopUp(tags, NULL);
12325     free(tags);
12326 }
12327
12328 void
12329 AnalyzeModeEvent()
12330 {
12331     if (appData.noChessProgram || gameMode == AnalyzeMode)
12332       return;
12333
12334     if (gameMode != AnalyzeFile) {
12335         if (!appData.icsEngineAnalyze) {
12336                EditGameEvent();
12337                if (gameMode != EditGame) return;
12338         }
12339         ResurrectChessProgram();
12340         SendToProgram("analyze\n", &first);
12341         first.analyzing = TRUE;
12342         /*first.maybeThinking = TRUE;*/
12343         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12344         EngineOutputPopUp();
12345     }
12346     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12347     pausing = FALSE;
12348     ModeHighlight();
12349     SetGameInfo();
12350
12351     StartAnalysisClock();
12352     GetTimeMark(&lastNodeCountTime);
12353     lastNodeCount = 0;
12354 }
12355
12356 void
12357 AnalyzeFileEvent()
12358 {
12359     if (appData.noChessProgram || gameMode == AnalyzeFile)
12360       return;
12361
12362     if (gameMode != AnalyzeMode) {
12363         EditGameEvent();
12364         if (gameMode != EditGame) return;
12365         ResurrectChessProgram();
12366         SendToProgram("analyze\n", &first);
12367         first.analyzing = TRUE;
12368         /*first.maybeThinking = TRUE;*/
12369         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12370         EngineOutputPopUp();
12371     }
12372     gameMode = AnalyzeFile;
12373     pausing = FALSE;
12374     ModeHighlight();
12375     SetGameInfo();
12376
12377     StartAnalysisClock();
12378     GetTimeMark(&lastNodeCountTime);
12379     lastNodeCount = 0;
12380 }
12381
12382 void
12383 MachineWhiteEvent()
12384 {
12385     char buf[MSG_SIZ];
12386     char *bookHit = NULL;
12387
12388     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12389       return;
12390
12391
12392     if (gameMode == PlayFromGameFile ||
12393         gameMode == TwoMachinesPlay  ||
12394         gameMode == Training         ||
12395         gameMode == AnalyzeMode      ||
12396         gameMode == EndOfGame)
12397         EditGameEvent();
12398
12399     if (gameMode == EditPosition)
12400         EditPositionDone(TRUE);
12401
12402     if (!WhiteOnMove(currentMove)) {
12403         DisplayError(_("It is not White's turn"), 0);
12404         return;
12405     }
12406
12407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12408       ExitAnalyzeMode();
12409
12410     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12411         gameMode == AnalyzeFile)
12412         TruncateGame();
12413
12414     ResurrectChessProgram();    /* in case it isn't running */
12415     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12416         gameMode = MachinePlaysWhite;
12417         ResetClocks();
12418     } else
12419     gameMode = MachinePlaysWhite;
12420     pausing = FALSE;
12421     ModeHighlight();
12422     SetGameInfo();
12423     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12424     DisplayTitle(buf);
12425     if (first.sendName) {
12426       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12427       SendToProgram(buf, &first);
12428     }
12429     if (first.sendTime) {
12430       if (first.useColors) {
12431         SendToProgram("black\n", &first); /*gnu kludge*/
12432       }
12433       SendTimeRemaining(&first, TRUE);
12434     }
12435     if (first.useColors) {
12436       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12437     }
12438     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12439     SetMachineThinkingEnables();
12440     first.maybeThinking = TRUE;
12441     StartClocks();
12442     firstMove = FALSE;
12443
12444     if (appData.autoFlipView && !flipView) {
12445       flipView = !flipView;
12446       DrawPosition(FALSE, NULL);
12447       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12448     }
12449
12450     if(bookHit) { // [HGM] book: simulate book reply
12451         static char bookMove[MSG_SIZ]; // a bit generous?
12452
12453         programStats.nodes = programStats.depth = programStats.time =
12454         programStats.score = programStats.got_only_move = 0;
12455         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12456
12457         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12458         strcat(bookMove, bookHit);
12459         HandleMachineMove(bookMove, &first);
12460     }
12461 }
12462
12463 void
12464 MachineBlackEvent()
12465 {
12466   char buf[MSG_SIZ];
12467   char *bookHit = NULL;
12468
12469     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12470         return;
12471
12472
12473     if (gameMode == PlayFromGameFile ||
12474         gameMode == TwoMachinesPlay  ||
12475         gameMode == Training         ||
12476         gameMode == AnalyzeMode      ||
12477         gameMode == EndOfGame)
12478         EditGameEvent();
12479
12480     if (gameMode == EditPosition)
12481         EditPositionDone(TRUE);
12482
12483     if (WhiteOnMove(currentMove)) {
12484         DisplayError(_("It is not Black's turn"), 0);
12485         return;
12486     }
12487
12488     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12489       ExitAnalyzeMode();
12490
12491     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12492         gameMode == AnalyzeFile)
12493         TruncateGame();
12494
12495     ResurrectChessProgram();    /* in case it isn't running */
12496     gameMode = MachinePlaysBlack;
12497     pausing = FALSE;
12498     ModeHighlight();
12499     SetGameInfo();
12500     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12501     DisplayTitle(buf);
12502     if (first.sendName) {
12503       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12504       SendToProgram(buf, &first);
12505     }
12506     if (first.sendTime) {
12507       if (first.useColors) {
12508         SendToProgram("white\n", &first); /*gnu kludge*/
12509       }
12510       SendTimeRemaining(&first, FALSE);
12511     }
12512     if (first.useColors) {
12513       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12514     }
12515     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12516     SetMachineThinkingEnables();
12517     first.maybeThinking = TRUE;
12518     StartClocks();
12519
12520     if (appData.autoFlipView && flipView) {
12521       flipView = !flipView;
12522       DrawPosition(FALSE, NULL);
12523       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12524     }
12525     if(bookHit) { // [HGM] book: simulate book reply
12526         static char bookMove[MSG_SIZ]; // a bit generous?
12527
12528         programStats.nodes = programStats.depth = programStats.time =
12529         programStats.score = programStats.got_only_move = 0;
12530         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12531
12532         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12533         strcat(bookMove, bookHit);
12534         HandleMachineMove(bookMove, &first);
12535     }
12536 }
12537
12538
12539 void
12540 DisplayTwoMachinesTitle()
12541 {
12542     char buf[MSG_SIZ];
12543     if (appData.matchGames > 0) {
12544         if (first.twoMachinesColor[0] == 'w') {
12545           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12546                    gameInfo.white, gameInfo.black,
12547                    first.matchWins, second.matchWins,
12548                    matchGame - 1 - (first.matchWins + second.matchWins));
12549         } else {
12550           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12551                    gameInfo.white, gameInfo.black,
12552                    second.matchWins, first.matchWins,
12553                    matchGame - 1 - (first.matchWins + second.matchWins));
12554         }
12555     } else {
12556       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12557     }
12558     DisplayTitle(buf);
12559 }
12560
12561 void
12562 SettingsMenuIfReady()
12563 {
12564   if (second.lastPing != second.lastPong) {
12565     DisplayMessage("", _("Waiting for second chess program"));
12566     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12567     return;
12568   }
12569   ThawUI();
12570   DisplayMessage("", "");
12571   SettingsPopUp(&second);
12572 }
12573
12574 int
12575 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12576 {
12577     char buf[MSG_SIZ];
12578     if (cps->pr == NULL) {
12579         StartChessProgram(cps);
12580         if (cps->protocolVersion == 1) {
12581           retry();
12582         } else {
12583           /* kludge: allow timeout for initial "feature" command */
12584           FreezeUI();
12585           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12586           DisplayMessage("", buf);
12587           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12588         }
12589         return 1;
12590     }
12591     return 0;
12592 }
12593
12594 void
12595 TwoMachinesEvent P((void))
12596 {
12597     int i;
12598     char buf[MSG_SIZ];
12599     ChessProgramState *onmove;
12600     char *bookHit = NULL;
12601     static int stalling = 0;
12602     TimeMark now;
12603     long wait;
12604
12605     if (appData.noChessProgram) return;
12606
12607     switch (gameMode) {
12608       case TwoMachinesPlay:
12609         return;
12610       case MachinePlaysWhite:
12611       case MachinePlaysBlack:
12612         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12613             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12614             return;
12615         }
12616         /* fall through */
12617       case BeginningOfGame:
12618       case PlayFromGameFile:
12619       case EndOfGame:
12620         EditGameEvent();
12621         if (gameMode != EditGame) return;
12622         break;
12623       case EditPosition:
12624         EditPositionDone(TRUE);
12625         break;
12626       case AnalyzeMode:
12627       case AnalyzeFile:
12628         ExitAnalyzeMode();
12629         break;
12630       case EditGame:
12631       default:
12632         break;
12633     }
12634
12635 //    forwardMostMove = currentMove;
12636     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12637
12638     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12639
12640     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12641     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12642       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12643       return;
12644     }
12645     if(!stalling) {
12646       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12647       SendToProgram("force\n", &second);
12648       stalling = 1;
12649       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12650       return;
12651     }
12652     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12653     if(appData.matchPause>10000 || appData.matchPause<10)
12654                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12655     wait = SubtractTimeMarks(&now, &pauseStart);
12656     if(wait < appData.matchPause) {
12657         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12658         return;
12659     }
12660     stalling = 0;
12661     DisplayMessage("", "");
12662     if (startedFromSetupPosition) {
12663         SendBoard(&second, backwardMostMove);
12664     if (appData.debugMode) {
12665         fprintf(debugFP, "Two Machines\n");
12666     }
12667     }
12668     for (i = backwardMostMove; i < forwardMostMove; i++) {
12669         SendMoveToProgram(i, &second);
12670     }
12671
12672     gameMode = TwoMachinesPlay;
12673     pausing = FALSE;
12674     ModeHighlight();
12675     SetGameInfo();
12676     DisplayTwoMachinesTitle();
12677     firstMove = TRUE;
12678     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12679         onmove = &first;
12680     } else {
12681         onmove = &second;
12682     }
12683     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12684     SendToProgram(first.computerString, &first);
12685     if (first.sendName) {
12686       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12687       SendToProgram(buf, &first);
12688     }
12689     SendToProgram(second.computerString, &second);
12690     if (second.sendName) {
12691       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12692       SendToProgram(buf, &second);
12693     }
12694
12695     ResetClocks();
12696     if (!first.sendTime || !second.sendTime) {
12697         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12698         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12699     }
12700     if (onmove->sendTime) {
12701       if (onmove->useColors) {
12702         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12703       }
12704       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12705     }
12706     if (onmove->useColors) {
12707       SendToProgram(onmove->twoMachinesColor, onmove);
12708     }
12709     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12710 //    SendToProgram("go\n", onmove);
12711     onmove->maybeThinking = TRUE;
12712     SetMachineThinkingEnables();
12713
12714     StartClocks();
12715
12716     if(bookHit) { // [HGM] book: simulate book reply
12717         static char bookMove[MSG_SIZ]; // a bit generous?
12718
12719         programStats.nodes = programStats.depth = programStats.time =
12720         programStats.score = programStats.got_only_move = 0;
12721         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12722
12723         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12724         strcat(bookMove, bookHit);
12725         savedMessage = bookMove; // args for deferred call
12726         savedState = onmove;
12727         ScheduleDelayedEvent(DeferredBookMove, 1);
12728     }
12729 }
12730
12731 void
12732 TrainingEvent()
12733 {
12734     if (gameMode == Training) {
12735       SetTrainingModeOff();
12736       gameMode = PlayFromGameFile;
12737       DisplayMessage("", _("Training mode off"));
12738     } else {
12739       gameMode = Training;
12740       animateTraining = appData.animate;
12741
12742       /* make sure we are not already at the end of the game */
12743       if (currentMove < forwardMostMove) {
12744         SetTrainingModeOn();
12745         DisplayMessage("", _("Training mode on"));
12746       } else {
12747         gameMode = PlayFromGameFile;
12748         DisplayError(_("Already at end of game"), 0);
12749       }
12750     }
12751     ModeHighlight();
12752 }
12753
12754 void
12755 IcsClientEvent()
12756 {
12757     if (!appData.icsActive) return;
12758     switch (gameMode) {
12759       case IcsPlayingWhite:
12760       case IcsPlayingBlack:
12761       case IcsObserving:
12762       case IcsIdle:
12763       case BeginningOfGame:
12764       case IcsExamining:
12765         return;
12766
12767       case EditGame:
12768         break;
12769
12770       case EditPosition:
12771         EditPositionDone(TRUE);
12772         break;
12773
12774       case AnalyzeMode:
12775       case AnalyzeFile:
12776         ExitAnalyzeMode();
12777         break;
12778
12779       default:
12780         EditGameEvent();
12781         break;
12782     }
12783
12784     gameMode = IcsIdle;
12785     ModeHighlight();
12786     return;
12787 }
12788
12789
12790 void
12791 EditGameEvent()
12792 {
12793     int i;
12794
12795     switch (gameMode) {
12796       case Training:
12797         SetTrainingModeOff();
12798         break;
12799       case MachinePlaysWhite:
12800       case MachinePlaysBlack:
12801       case BeginningOfGame:
12802         SendToProgram("force\n", &first);
12803         SetUserThinkingEnables();
12804         break;
12805       case PlayFromGameFile:
12806         (void) StopLoadGameTimer();
12807         if (gameFileFP != NULL) {
12808             gameFileFP = NULL;
12809         }
12810         break;
12811       case EditPosition:
12812         EditPositionDone(TRUE);
12813         break;
12814       case AnalyzeMode:
12815       case AnalyzeFile:
12816         ExitAnalyzeMode();
12817         SendToProgram("force\n", &first);
12818         break;
12819       case TwoMachinesPlay:
12820         GameEnds(EndOfFile, NULL, GE_PLAYER);
12821         ResurrectChessProgram();
12822         SetUserThinkingEnables();
12823         break;
12824       case EndOfGame:
12825         ResurrectChessProgram();
12826         break;
12827       case IcsPlayingBlack:
12828       case IcsPlayingWhite:
12829         DisplayError(_("Warning: You are still playing a game"), 0);
12830         break;
12831       case IcsObserving:
12832         DisplayError(_("Warning: You are still observing a game"), 0);
12833         break;
12834       case IcsExamining:
12835         DisplayError(_("Warning: You are still examining a game"), 0);
12836         break;
12837       case IcsIdle:
12838         break;
12839       case EditGame:
12840       default:
12841         return;
12842     }
12843
12844     pausing = FALSE;
12845     StopClocks();
12846     first.offeredDraw = second.offeredDraw = 0;
12847
12848     if (gameMode == PlayFromGameFile) {
12849         whiteTimeRemaining = timeRemaining[0][currentMove];
12850         blackTimeRemaining = timeRemaining[1][currentMove];
12851         DisplayTitle("");
12852     }
12853
12854     if (gameMode == MachinePlaysWhite ||
12855         gameMode == MachinePlaysBlack ||
12856         gameMode == TwoMachinesPlay ||
12857         gameMode == EndOfGame) {
12858         i = forwardMostMove;
12859         while (i > currentMove) {
12860             SendToProgram("undo\n", &first);
12861             i--;
12862         }
12863         whiteTimeRemaining = timeRemaining[0][currentMove];
12864         blackTimeRemaining = timeRemaining[1][currentMove];
12865         DisplayBothClocks();
12866         if (whiteFlag || blackFlag) {
12867             whiteFlag = blackFlag = 0;
12868         }
12869         DisplayTitle("");
12870     }
12871
12872     gameMode = EditGame;
12873     ModeHighlight();
12874     SetGameInfo();
12875 }
12876
12877
12878 void
12879 EditPositionEvent()
12880 {
12881     if (gameMode == EditPosition) {
12882         EditGameEvent();
12883         return;
12884     }
12885
12886     EditGameEvent();
12887     if (gameMode != EditGame) return;
12888
12889     gameMode = EditPosition;
12890     ModeHighlight();
12891     SetGameInfo();
12892     if (currentMove > 0)
12893       CopyBoard(boards[0], boards[currentMove]);
12894
12895     blackPlaysFirst = !WhiteOnMove(currentMove);
12896     ResetClocks();
12897     currentMove = forwardMostMove = backwardMostMove = 0;
12898     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12899     DisplayMove(-1);
12900 }
12901
12902 void
12903 ExitAnalyzeMode()
12904 {
12905     /* [DM] icsEngineAnalyze - possible call from other functions */
12906     if (appData.icsEngineAnalyze) {
12907         appData.icsEngineAnalyze = FALSE;
12908
12909         DisplayMessage("",_("Close ICS engine analyze..."));
12910     }
12911     if (first.analysisSupport && first.analyzing) {
12912       SendToProgram("exit\n", &first);
12913       first.analyzing = FALSE;
12914     }
12915     thinkOutput[0] = NULLCHAR;
12916 }
12917
12918 void
12919 EditPositionDone(Boolean fakeRights)
12920 {
12921     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12922
12923     startedFromSetupPosition = TRUE;
12924     InitChessProgram(&first, FALSE);
12925     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12926       boards[0][EP_STATUS] = EP_NONE;
12927       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12928     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12929         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12930         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12931       } else boards[0][CASTLING][2] = NoRights;
12932     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12933         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12934         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12935       } else boards[0][CASTLING][5] = NoRights;
12936     }
12937     SendToProgram("force\n", &first);
12938     if (blackPlaysFirst) {
12939         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12940         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12941         currentMove = forwardMostMove = backwardMostMove = 1;
12942         CopyBoard(boards[1], boards[0]);
12943     } else {
12944         currentMove = forwardMostMove = backwardMostMove = 0;
12945     }
12946     SendBoard(&first, forwardMostMove);
12947     if (appData.debugMode) {
12948         fprintf(debugFP, "EditPosDone\n");
12949     }
12950     DisplayTitle("");
12951     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12952     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12953     gameMode = EditGame;
12954     ModeHighlight();
12955     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12956     ClearHighlights(); /* [AS] */
12957 }
12958
12959 /* Pause for `ms' milliseconds */
12960 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12961 void
12962 TimeDelay(ms)
12963      long ms;
12964 {
12965     TimeMark m1, m2;
12966
12967     GetTimeMark(&m1);
12968     do {
12969         GetTimeMark(&m2);
12970     } while (SubtractTimeMarks(&m2, &m1) < ms);
12971 }
12972
12973 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12974 void
12975 SendMultiLineToICS(buf)
12976      char *buf;
12977 {
12978     char temp[MSG_SIZ+1], *p;
12979     int len;
12980
12981     len = strlen(buf);
12982     if (len > MSG_SIZ)
12983       len = MSG_SIZ;
12984
12985     strncpy(temp, buf, len);
12986     temp[len] = 0;
12987
12988     p = temp;
12989     while (*p) {
12990         if (*p == '\n' || *p == '\r')
12991           *p = ' ';
12992         ++p;
12993     }
12994
12995     strcat(temp, "\n");
12996     SendToICS(temp);
12997     SendToPlayer(temp, strlen(temp));
12998 }
12999
13000 void
13001 SetWhiteToPlayEvent()
13002 {
13003     if (gameMode == EditPosition) {
13004         blackPlaysFirst = FALSE;
13005         DisplayBothClocks();    /* works because currentMove is 0 */
13006     } else if (gameMode == IcsExamining) {
13007         SendToICS(ics_prefix);
13008         SendToICS("tomove white\n");
13009     }
13010 }
13011
13012 void
13013 SetBlackToPlayEvent()
13014 {
13015     if (gameMode == EditPosition) {
13016         blackPlaysFirst = TRUE;
13017         currentMove = 1;        /* kludge */
13018         DisplayBothClocks();
13019         currentMove = 0;
13020     } else if (gameMode == IcsExamining) {
13021         SendToICS(ics_prefix);
13022         SendToICS("tomove black\n");
13023     }
13024 }
13025
13026 void
13027 EditPositionMenuEvent(selection, x, y)
13028      ChessSquare selection;
13029      int x, y;
13030 {
13031     char buf[MSG_SIZ];
13032     ChessSquare piece = boards[0][y][x];
13033
13034     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13035
13036     switch (selection) {
13037       case ClearBoard:
13038         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13039             SendToICS(ics_prefix);
13040             SendToICS("bsetup clear\n");
13041         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13042             SendToICS(ics_prefix);
13043             SendToICS("clearboard\n");
13044         } else {
13045             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13046                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13047                 for (y = 0; y < BOARD_HEIGHT; y++) {
13048                     if (gameMode == IcsExamining) {
13049                         if (boards[currentMove][y][x] != EmptySquare) {
13050                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13051                                     AAA + x, ONE + y);
13052                             SendToICS(buf);
13053                         }
13054                     } else {
13055                         boards[0][y][x] = p;
13056                     }
13057                 }
13058             }
13059         }
13060         if (gameMode == EditPosition) {
13061             DrawPosition(FALSE, boards[0]);
13062         }
13063         break;
13064
13065       case WhitePlay:
13066         SetWhiteToPlayEvent();
13067         break;
13068
13069       case BlackPlay:
13070         SetBlackToPlayEvent();
13071         break;
13072
13073       case EmptySquare:
13074         if (gameMode == IcsExamining) {
13075             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13076             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13077             SendToICS(buf);
13078         } else {
13079             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13080                 if(x == BOARD_LEFT-2) {
13081                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13082                     boards[0][y][1] = 0;
13083                 } else
13084                 if(x == BOARD_RGHT+1) {
13085                     if(y >= gameInfo.holdingsSize) break;
13086                     boards[0][y][BOARD_WIDTH-2] = 0;
13087                 } else break;
13088             }
13089             boards[0][y][x] = EmptySquare;
13090             DrawPosition(FALSE, boards[0]);
13091         }
13092         break;
13093
13094       case PromotePiece:
13095         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13096            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13097             selection = (ChessSquare) (PROMOTED piece);
13098         } else if(piece == EmptySquare) selection = WhiteSilver;
13099         else selection = (ChessSquare)((int)piece - 1);
13100         goto defaultlabel;
13101
13102       case DemotePiece:
13103         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13104            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13105             selection = (ChessSquare) (DEMOTED piece);
13106         } else if(piece == EmptySquare) selection = BlackSilver;
13107         else selection = (ChessSquare)((int)piece + 1);
13108         goto defaultlabel;
13109
13110       case WhiteQueen:
13111       case BlackQueen:
13112         if(gameInfo.variant == VariantShatranj ||
13113            gameInfo.variant == VariantXiangqi  ||
13114            gameInfo.variant == VariantCourier  ||
13115            gameInfo.variant == VariantMakruk     )
13116             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13117         goto defaultlabel;
13118
13119       case WhiteKing:
13120       case BlackKing:
13121         if(gameInfo.variant == VariantXiangqi)
13122             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13123         if(gameInfo.variant == VariantKnightmate)
13124             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13125       default:
13126         defaultlabel:
13127         if (gameMode == IcsExamining) {
13128             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13129             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13130                      PieceToChar(selection), AAA + x, ONE + y);
13131             SendToICS(buf);
13132         } else {
13133             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13134                 int n;
13135                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13136                     n = PieceToNumber(selection - BlackPawn);
13137                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13138                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13139                     boards[0][BOARD_HEIGHT-1-n][1]++;
13140                 } else
13141                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13142                     n = PieceToNumber(selection);
13143                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13144                     boards[0][n][BOARD_WIDTH-1] = selection;
13145                     boards[0][n][BOARD_WIDTH-2]++;
13146                 }
13147             } else
13148             boards[0][y][x] = selection;
13149             DrawPosition(TRUE, boards[0]);
13150         }
13151         break;
13152     }
13153 }
13154
13155
13156 void
13157 DropMenuEvent(selection, x, y)
13158      ChessSquare selection;
13159      int x, y;
13160 {
13161     ChessMove moveType;
13162
13163     switch (gameMode) {
13164       case IcsPlayingWhite:
13165       case MachinePlaysBlack:
13166         if (!WhiteOnMove(currentMove)) {
13167             DisplayMoveError(_("It is Black's turn"));
13168             return;
13169         }
13170         moveType = WhiteDrop;
13171         break;
13172       case IcsPlayingBlack:
13173       case MachinePlaysWhite:
13174         if (WhiteOnMove(currentMove)) {
13175             DisplayMoveError(_("It is White's turn"));
13176             return;
13177         }
13178         moveType = BlackDrop;
13179         break;
13180       case EditGame:
13181         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13182         break;
13183       default:
13184         return;
13185     }
13186
13187     if (moveType == BlackDrop && selection < BlackPawn) {
13188       selection = (ChessSquare) ((int) selection
13189                                  + (int) BlackPawn - (int) WhitePawn);
13190     }
13191     if (boards[currentMove][y][x] != EmptySquare) {
13192         DisplayMoveError(_("That square is occupied"));
13193         return;
13194     }
13195
13196     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13197 }
13198
13199 void
13200 AcceptEvent()
13201 {
13202     /* Accept a pending offer of any kind from opponent */
13203
13204     if (appData.icsActive) {
13205         SendToICS(ics_prefix);
13206         SendToICS("accept\n");
13207     } else if (cmailMsgLoaded) {
13208         if (currentMove == cmailOldMove &&
13209             commentList[cmailOldMove] != NULL &&
13210             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13211                    "Black offers a draw" : "White offers a draw")) {
13212             TruncateGame();
13213             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13214             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13215         } else {
13216             DisplayError(_("There is no pending offer on this move"), 0);
13217             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13218         }
13219     } else {
13220         /* Not used for offers from chess program */
13221     }
13222 }
13223
13224 void
13225 DeclineEvent()
13226 {
13227     /* Decline a pending offer of any kind from opponent */
13228
13229     if (appData.icsActive) {
13230         SendToICS(ics_prefix);
13231         SendToICS("decline\n");
13232     } else if (cmailMsgLoaded) {
13233         if (currentMove == cmailOldMove &&
13234             commentList[cmailOldMove] != NULL &&
13235             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13236                    "Black offers a draw" : "White offers a draw")) {
13237 #ifdef NOTDEF
13238             AppendComment(cmailOldMove, "Draw declined", TRUE);
13239             DisplayComment(cmailOldMove - 1, "Draw declined");
13240 #endif /*NOTDEF*/
13241         } else {
13242             DisplayError(_("There is no pending offer on this move"), 0);
13243         }
13244     } else {
13245         /* Not used for offers from chess program */
13246     }
13247 }
13248
13249 void
13250 RematchEvent()
13251 {
13252     /* Issue ICS rematch command */
13253     if (appData.icsActive) {
13254         SendToICS(ics_prefix);
13255         SendToICS("rematch\n");
13256     }
13257 }
13258
13259 void
13260 CallFlagEvent()
13261 {
13262     /* Call your opponent's flag (claim a win on time) */
13263     if (appData.icsActive) {
13264         SendToICS(ics_prefix);
13265         SendToICS("flag\n");
13266     } else {
13267         switch (gameMode) {
13268           default:
13269             return;
13270           case MachinePlaysWhite:
13271             if (whiteFlag) {
13272                 if (blackFlag)
13273                   GameEnds(GameIsDrawn, "Both players ran out of time",
13274                            GE_PLAYER);
13275                 else
13276                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13277             } else {
13278                 DisplayError(_("Your opponent is not out of time"), 0);
13279             }
13280             break;
13281           case MachinePlaysBlack:
13282             if (blackFlag) {
13283                 if (whiteFlag)
13284                   GameEnds(GameIsDrawn, "Both players ran out of time",
13285                            GE_PLAYER);
13286                 else
13287                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13288             } else {
13289                 DisplayError(_("Your opponent is not out of time"), 0);
13290             }
13291             break;
13292         }
13293     }
13294 }
13295
13296 void
13297 ClockClick(int which)
13298 {       // [HGM] code moved to back-end from winboard.c
13299         if(which) { // black clock
13300           if (gameMode == EditPosition || gameMode == IcsExamining) {
13301             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13302             SetBlackToPlayEvent();
13303           } else if (gameMode == EditGame || shiftKey) {
13304             AdjustClock(which, -1);
13305           } else if (gameMode == IcsPlayingWhite ||
13306                      gameMode == MachinePlaysBlack) {
13307             CallFlagEvent();
13308           }
13309         } else { // white clock
13310           if (gameMode == EditPosition || gameMode == IcsExamining) {
13311             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13312             SetWhiteToPlayEvent();
13313           } else if (gameMode == EditGame || shiftKey) {
13314             AdjustClock(which, -1);
13315           } else if (gameMode == IcsPlayingBlack ||
13316                    gameMode == MachinePlaysWhite) {
13317             CallFlagEvent();
13318           }
13319         }
13320 }
13321
13322 void
13323 DrawEvent()
13324 {
13325     /* Offer draw or accept pending draw offer from opponent */
13326
13327     if (appData.icsActive) {
13328         /* Note: tournament rules require draw offers to be
13329            made after you make your move but before you punch
13330            your clock.  Currently ICS doesn't let you do that;
13331            instead, you immediately punch your clock after making
13332            a move, but you can offer a draw at any time. */
13333
13334         SendToICS(ics_prefix);
13335         SendToICS("draw\n");
13336         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13337     } else if (cmailMsgLoaded) {
13338         if (currentMove == cmailOldMove &&
13339             commentList[cmailOldMove] != NULL &&
13340             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13341                    "Black offers a draw" : "White offers a draw")) {
13342             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13344         } else if (currentMove == cmailOldMove + 1) {
13345             char *offer = WhiteOnMove(cmailOldMove) ?
13346               "White offers a draw" : "Black offers a draw";
13347             AppendComment(currentMove, offer, TRUE);
13348             DisplayComment(currentMove - 1, offer);
13349             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13350         } else {
13351             DisplayError(_("You must make your move before offering a draw"), 0);
13352             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13353         }
13354     } else if (first.offeredDraw) {
13355         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13356     } else {
13357         if (first.sendDrawOffers) {
13358             SendToProgram("draw\n", &first);
13359             userOfferedDraw = TRUE;
13360         }
13361     }
13362 }
13363
13364 void
13365 AdjournEvent()
13366 {
13367     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13368
13369     if (appData.icsActive) {
13370         SendToICS(ics_prefix);
13371         SendToICS("adjourn\n");
13372     } else {
13373         /* Currently GNU Chess doesn't offer or accept Adjourns */
13374     }
13375 }
13376
13377
13378 void
13379 AbortEvent()
13380 {
13381     /* Offer Abort or accept pending Abort offer from opponent */
13382
13383     if (appData.icsActive) {
13384         SendToICS(ics_prefix);
13385         SendToICS("abort\n");
13386     } else {
13387         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13388     }
13389 }
13390
13391 void
13392 ResignEvent()
13393 {
13394     /* Resign.  You can do this even if it's not your turn. */
13395
13396     if (appData.icsActive) {
13397         SendToICS(ics_prefix);
13398         SendToICS("resign\n");
13399     } else {
13400         switch (gameMode) {
13401           case MachinePlaysWhite:
13402             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13403             break;
13404           case MachinePlaysBlack:
13405             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13406             break;
13407           case EditGame:
13408             if (cmailMsgLoaded) {
13409                 TruncateGame();
13410                 if (WhiteOnMove(cmailOldMove)) {
13411                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13412                 } else {
13413                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13414                 }
13415                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13416             }
13417             break;
13418           default:
13419             break;
13420         }
13421     }
13422 }
13423
13424
13425 void
13426 StopObservingEvent()
13427 {
13428     /* Stop observing current games */
13429     SendToICS(ics_prefix);
13430     SendToICS("unobserve\n");
13431 }
13432
13433 void
13434 StopExaminingEvent()
13435 {
13436     /* Stop observing current game */
13437     SendToICS(ics_prefix);
13438     SendToICS("unexamine\n");
13439 }
13440
13441 void
13442 ForwardInner(target)
13443      int target;
13444 {
13445     int limit;
13446
13447     if (appData.debugMode)
13448         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13449                 target, currentMove, forwardMostMove);
13450
13451     if (gameMode == EditPosition)
13452       return;
13453
13454     if (gameMode == PlayFromGameFile && !pausing)
13455       PauseEvent();
13456
13457     if (gameMode == IcsExamining && pausing)
13458       limit = pauseExamForwardMostMove;
13459     else
13460       limit = forwardMostMove;
13461
13462     if (target > limit) target = limit;
13463
13464     if (target > 0 && moveList[target - 1][0]) {
13465         int fromX, fromY, toX, toY;
13466         toX = moveList[target - 1][2] - AAA;
13467         toY = moveList[target - 1][3] - ONE;
13468         if (moveList[target - 1][1] == '@') {
13469             if (appData.highlightLastMove) {
13470                 SetHighlights(-1, -1, toX, toY);
13471             }
13472         } else {
13473             fromX = moveList[target - 1][0] - AAA;
13474             fromY = moveList[target - 1][1] - ONE;
13475             if (target == currentMove + 1) {
13476                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13477             }
13478             if (appData.highlightLastMove) {
13479                 SetHighlights(fromX, fromY, toX, toY);
13480             }
13481         }
13482     }
13483     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13484         gameMode == Training || gameMode == PlayFromGameFile ||
13485         gameMode == AnalyzeFile) {
13486         while (currentMove < target) {
13487             SendMoveToProgram(currentMove++, &first);
13488         }
13489     } else {
13490         currentMove = target;
13491     }
13492
13493     if (gameMode == EditGame || gameMode == EndOfGame) {
13494         whiteTimeRemaining = timeRemaining[0][currentMove];
13495         blackTimeRemaining = timeRemaining[1][currentMove];
13496     }
13497     DisplayBothClocks();
13498     DisplayMove(currentMove - 1);
13499     DrawPosition(FALSE, boards[currentMove]);
13500     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13501     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13502         DisplayComment(currentMove - 1, commentList[currentMove]);
13503     }
13504     DisplayBook(currentMove);
13505 }
13506
13507
13508 void
13509 ForwardEvent()
13510 {
13511     if (gameMode == IcsExamining && !pausing) {
13512         SendToICS(ics_prefix);
13513         SendToICS("forward\n");
13514     } else {
13515         ForwardInner(currentMove + 1);
13516     }
13517 }
13518
13519 void
13520 ToEndEvent()
13521 {
13522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13523         /* to optimze, we temporarily turn off analysis mode while we feed
13524          * the remaining moves to the engine. Otherwise we get analysis output
13525          * after each move.
13526          */
13527         if (first.analysisSupport) {
13528           SendToProgram("exit\nforce\n", &first);
13529           first.analyzing = FALSE;
13530         }
13531     }
13532
13533     if (gameMode == IcsExamining && !pausing) {
13534         SendToICS(ics_prefix);
13535         SendToICS("forward 999999\n");
13536     } else {
13537         ForwardInner(forwardMostMove);
13538     }
13539
13540     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13541         /* we have fed all the moves, so reactivate analysis mode */
13542         SendToProgram("analyze\n", &first);
13543         first.analyzing = TRUE;
13544         /*first.maybeThinking = TRUE;*/
13545         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13546     }
13547 }
13548
13549 void
13550 BackwardInner(target)
13551      int target;
13552 {
13553     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13554
13555     if (appData.debugMode)
13556         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13557                 target, currentMove, forwardMostMove);
13558
13559     if (gameMode == EditPosition) return;
13560     if (currentMove <= backwardMostMove) {
13561         ClearHighlights();
13562         DrawPosition(full_redraw, boards[currentMove]);
13563         return;
13564     }
13565     if (gameMode == PlayFromGameFile && !pausing)
13566       PauseEvent();
13567
13568     if (moveList[target][0]) {
13569         int fromX, fromY, toX, toY;
13570         toX = moveList[target][2] - AAA;
13571         toY = moveList[target][3] - ONE;
13572         if (moveList[target][1] == '@') {
13573             if (appData.highlightLastMove) {
13574                 SetHighlights(-1, -1, toX, toY);
13575             }
13576         } else {
13577             fromX = moveList[target][0] - AAA;
13578             fromY = moveList[target][1] - ONE;
13579             if (target == currentMove - 1) {
13580                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13581             }
13582             if (appData.highlightLastMove) {
13583                 SetHighlights(fromX, fromY, toX, toY);
13584             }
13585         }
13586     }
13587     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13588         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13589         while (currentMove > target) {
13590             SendToProgram("undo\n", &first);
13591             currentMove--;
13592         }
13593     } else {
13594         currentMove = target;
13595     }
13596
13597     if (gameMode == EditGame || gameMode == EndOfGame) {
13598         whiteTimeRemaining = timeRemaining[0][currentMove];
13599         blackTimeRemaining = timeRemaining[1][currentMove];
13600     }
13601     DisplayBothClocks();
13602     DisplayMove(currentMove - 1);
13603     DrawPosition(full_redraw, boards[currentMove]);
13604     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13605     // [HGM] PV info: routine tests if comment empty
13606     DisplayComment(currentMove - 1, commentList[currentMove]);
13607     DisplayBook(currentMove);
13608 }
13609
13610 void
13611 BackwardEvent()
13612 {
13613     if (gameMode == IcsExamining && !pausing) {
13614         SendToICS(ics_prefix);
13615         SendToICS("backward\n");
13616     } else {
13617         BackwardInner(currentMove - 1);
13618     }
13619 }
13620
13621 void
13622 ToStartEvent()
13623 {
13624     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13625         /* to optimize, we temporarily turn off analysis mode while we undo
13626          * all the moves. Otherwise we get analysis output after each undo.
13627          */
13628         if (first.analysisSupport) {
13629           SendToProgram("exit\nforce\n", &first);
13630           first.analyzing = FALSE;
13631         }
13632     }
13633
13634     if (gameMode == IcsExamining && !pausing) {
13635         SendToICS(ics_prefix);
13636         SendToICS("backward 999999\n");
13637     } else {
13638         BackwardInner(backwardMostMove);
13639     }
13640
13641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13642         /* we have fed all the moves, so reactivate analysis mode */
13643         SendToProgram("analyze\n", &first);
13644         first.analyzing = TRUE;
13645         /*first.maybeThinking = TRUE;*/
13646         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13647     }
13648 }
13649
13650 void
13651 ToNrEvent(int to)
13652 {
13653   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13654   if (to >= forwardMostMove) to = forwardMostMove;
13655   if (to <= backwardMostMove) to = backwardMostMove;
13656   if (to < currentMove) {
13657     BackwardInner(to);
13658   } else {
13659     ForwardInner(to);
13660   }
13661 }
13662
13663 void
13664 RevertEvent(Boolean annotate)
13665 {
13666     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13667         return;
13668     }
13669     if (gameMode != IcsExamining) {
13670         DisplayError(_("You are not examining a game"), 0);
13671         return;
13672     }
13673     if (pausing) {
13674         DisplayError(_("You can't revert while pausing"), 0);
13675         return;
13676     }
13677     SendToICS(ics_prefix);
13678     SendToICS("revert\n");
13679 }
13680
13681 void
13682 RetractMoveEvent()
13683 {
13684     switch (gameMode) {
13685       case MachinePlaysWhite:
13686       case MachinePlaysBlack:
13687         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13688             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13689             return;
13690         }
13691         if (forwardMostMove < 2) return;
13692         currentMove = forwardMostMove = forwardMostMove - 2;
13693         whiteTimeRemaining = timeRemaining[0][currentMove];
13694         blackTimeRemaining = timeRemaining[1][currentMove];
13695         DisplayBothClocks();
13696         DisplayMove(currentMove - 1);
13697         ClearHighlights();/*!! could figure this out*/
13698         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13699         SendToProgram("remove\n", &first);
13700         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13701         break;
13702
13703       case BeginningOfGame:
13704       default:
13705         break;
13706
13707       case IcsPlayingWhite:
13708       case IcsPlayingBlack:
13709         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13710             SendToICS(ics_prefix);
13711             SendToICS("takeback 2\n");
13712         } else {
13713             SendToICS(ics_prefix);
13714             SendToICS("takeback 1\n");
13715         }
13716         break;
13717     }
13718 }
13719
13720 void
13721 MoveNowEvent()
13722 {
13723     ChessProgramState *cps;
13724
13725     switch (gameMode) {
13726       case MachinePlaysWhite:
13727         if (!WhiteOnMove(forwardMostMove)) {
13728             DisplayError(_("It is your turn"), 0);
13729             return;
13730         }
13731         cps = &first;
13732         break;
13733       case MachinePlaysBlack:
13734         if (WhiteOnMove(forwardMostMove)) {
13735             DisplayError(_("It is your turn"), 0);
13736             return;
13737         }
13738         cps = &first;
13739         break;
13740       case TwoMachinesPlay:
13741         if (WhiteOnMove(forwardMostMove) ==
13742             (first.twoMachinesColor[0] == 'w')) {
13743             cps = &first;
13744         } else {
13745             cps = &second;
13746         }
13747         break;
13748       case BeginningOfGame:
13749       default:
13750         return;
13751     }
13752     SendToProgram("?\n", cps);
13753 }
13754
13755 void
13756 TruncateGameEvent()
13757 {
13758     EditGameEvent();
13759     if (gameMode != EditGame) return;
13760     TruncateGame();
13761 }
13762
13763 void
13764 TruncateGame()
13765 {
13766     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13767     if (forwardMostMove > currentMove) {
13768         if (gameInfo.resultDetails != NULL) {
13769             free(gameInfo.resultDetails);
13770             gameInfo.resultDetails = NULL;
13771             gameInfo.result = GameUnfinished;
13772         }
13773         forwardMostMove = currentMove;
13774         HistorySet(parseList, backwardMostMove, forwardMostMove,
13775                    currentMove-1);
13776     }
13777 }
13778
13779 void
13780 HintEvent()
13781 {
13782     if (appData.noChessProgram) return;
13783     switch (gameMode) {
13784       case MachinePlaysWhite:
13785         if (WhiteOnMove(forwardMostMove)) {
13786             DisplayError(_("Wait until your turn"), 0);
13787             return;
13788         }
13789         break;
13790       case BeginningOfGame:
13791       case MachinePlaysBlack:
13792         if (!WhiteOnMove(forwardMostMove)) {
13793             DisplayError(_("Wait until your turn"), 0);
13794             return;
13795         }
13796         break;
13797       default:
13798         DisplayError(_("No hint available"), 0);
13799         return;
13800     }
13801     SendToProgram("hint\n", &first);
13802     hintRequested = TRUE;
13803 }
13804
13805 void
13806 BookEvent()
13807 {
13808     if (appData.noChessProgram) return;
13809     switch (gameMode) {
13810       case MachinePlaysWhite:
13811         if (WhiteOnMove(forwardMostMove)) {
13812             DisplayError(_("Wait until your turn"), 0);
13813             return;
13814         }
13815         break;
13816       case BeginningOfGame:
13817       case MachinePlaysBlack:
13818         if (!WhiteOnMove(forwardMostMove)) {
13819             DisplayError(_("Wait until your turn"), 0);
13820             return;
13821         }
13822         break;
13823       case EditPosition:
13824         EditPositionDone(TRUE);
13825         break;
13826       case TwoMachinesPlay:
13827         return;
13828       default:
13829         break;
13830     }
13831     SendToProgram("bk\n", &first);
13832     bookOutput[0] = NULLCHAR;
13833     bookRequested = TRUE;
13834 }
13835
13836 void
13837 AboutGameEvent()
13838 {
13839     char *tags = PGNTags(&gameInfo);
13840     TagsPopUp(tags, CmailMsg());
13841     free(tags);
13842 }
13843
13844 /* end button procedures */
13845
13846 void
13847 PrintPosition(fp, move)
13848      FILE *fp;
13849      int move;
13850 {
13851     int i, j;
13852
13853     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13854         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13855             char c = PieceToChar(boards[move][i][j]);
13856             fputc(c == 'x' ? '.' : c, fp);
13857             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13858         }
13859     }
13860     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13861       fprintf(fp, "white to play\n");
13862     else
13863       fprintf(fp, "black to play\n");
13864 }
13865
13866 void
13867 PrintOpponents(fp)
13868      FILE *fp;
13869 {
13870     if (gameInfo.white != NULL) {
13871         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13872     } else {
13873         fprintf(fp, "\n");
13874     }
13875 }
13876
13877 /* Find last component of program's own name, using some heuristics */
13878 void
13879 TidyProgramName(prog, host, buf)
13880      char *prog, *host, buf[MSG_SIZ];
13881 {
13882     char *p, *q;
13883     int local = (strcmp(host, "localhost") == 0);
13884     while (!local && (p = strchr(prog, ';')) != NULL) {
13885         p++;
13886         while (*p == ' ') p++;
13887         prog = p;
13888     }
13889     if (*prog == '"' || *prog == '\'') {
13890         q = strchr(prog + 1, *prog);
13891     } else {
13892         q = strchr(prog, ' ');
13893     }
13894     if (q == NULL) q = prog + strlen(prog);
13895     p = q;
13896     while (p >= prog && *p != '/' && *p != '\\') p--;
13897     p++;
13898     if(p == prog && *p == '"') p++;
13899     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13900     memcpy(buf, p, q - p);
13901     buf[q - p] = NULLCHAR;
13902     if (!local) {
13903         strcat(buf, "@");
13904         strcat(buf, host);
13905     }
13906 }
13907
13908 char *
13909 TimeControlTagValue()
13910 {
13911     char buf[MSG_SIZ];
13912     if (!appData.clockMode) {
13913       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13914     } else if (movesPerSession > 0) {
13915       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13916     } else if (timeIncrement == 0) {
13917       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13918     } else {
13919       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13920     }
13921     return StrSave(buf);
13922 }
13923
13924 void
13925 SetGameInfo()
13926 {
13927     /* This routine is used only for certain modes */
13928     VariantClass v = gameInfo.variant;
13929     ChessMove r = GameUnfinished;
13930     char *p = NULL;
13931
13932     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13933         r = gameInfo.result;
13934         p = gameInfo.resultDetails;
13935         gameInfo.resultDetails = NULL;
13936     }
13937     ClearGameInfo(&gameInfo);
13938     gameInfo.variant = v;
13939
13940     switch (gameMode) {
13941       case MachinePlaysWhite:
13942         gameInfo.event = StrSave( appData.pgnEventHeader );
13943         gameInfo.site = StrSave(HostName());
13944         gameInfo.date = PGNDate();
13945         gameInfo.round = StrSave("-");
13946         gameInfo.white = StrSave(first.tidy);
13947         gameInfo.black = StrSave(UserName());
13948         gameInfo.timeControl = TimeControlTagValue();
13949         break;
13950
13951       case MachinePlaysBlack:
13952         gameInfo.event = StrSave( appData.pgnEventHeader );
13953         gameInfo.site = StrSave(HostName());
13954         gameInfo.date = PGNDate();
13955         gameInfo.round = StrSave("-");
13956         gameInfo.white = StrSave(UserName());
13957         gameInfo.black = StrSave(first.tidy);
13958         gameInfo.timeControl = TimeControlTagValue();
13959         break;
13960
13961       case TwoMachinesPlay:
13962         gameInfo.event = StrSave( appData.pgnEventHeader );
13963         gameInfo.site = StrSave(HostName());
13964         gameInfo.date = PGNDate();
13965         if (roundNr > 0) {
13966             char buf[MSG_SIZ];
13967             snprintf(buf, MSG_SIZ, "%d", roundNr);
13968             gameInfo.round = StrSave(buf);
13969         } else {
13970             gameInfo.round = StrSave("-");
13971         }
13972         if (first.twoMachinesColor[0] == 'w') {
13973             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13974             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13975         } else {
13976             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
13977             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
13978         }
13979         gameInfo.timeControl = TimeControlTagValue();
13980         break;
13981
13982       case EditGame:
13983         gameInfo.event = StrSave("Edited game");
13984         gameInfo.site = StrSave(HostName());
13985         gameInfo.date = PGNDate();
13986         gameInfo.round = StrSave("-");
13987         gameInfo.white = StrSave("-");
13988         gameInfo.black = StrSave("-");
13989         gameInfo.result = r;
13990         gameInfo.resultDetails = p;
13991         break;
13992
13993       case EditPosition:
13994         gameInfo.event = StrSave("Edited position");
13995         gameInfo.site = StrSave(HostName());
13996         gameInfo.date = PGNDate();
13997         gameInfo.round = StrSave("-");
13998         gameInfo.white = StrSave("-");
13999         gameInfo.black = StrSave("-");
14000         break;
14001
14002       case IcsPlayingWhite:
14003       case IcsPlayingBlack:
14004       case IcsObserving:
14005       case IcsExamining:
14006         break;
14007
14008       case PlayFromGameFile:
14009         gameInfo.event = StrSave("Game from non-PGN file");
14010         gameInfo.site = StrSave(HostName());
14011         gameInfo.date = PGNDate();
14012         gameInfo.round = StrSave("-");
14013         gameInfo.white = StrSave("?");
14014         gameInfo.black = StrSave("?");
14015         break;
14016
14017       default:
14018         break;
14019     }
14020 }
14021
14022 void
14023 ReplaceComment(index, text)
14024      int index;
14025      char *text;
14026 {
14027     int len;
14028     char *p;
14029     float score;
14030
14031     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14032        pvInfoList[index-1].depth == len &&
14033        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14034        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14035     while (*text == '\n') text++;
14036     len = strlen(text);
14037     while (len > 0 && text[len - 1] == '\n') len--;
14038
14039     if (commentList[index] != NULL)
14040       free(commentList[index]);
14041
14042     if (len == 0) {
14043         commentList[index] = NULL;
14044         return;
14045     }
14046   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14047       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14048       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14049     commentList[index] = (char *) malloc(len + 2);
14050     strncpy(commentList[index], text, len);
14051     commentList[index][len] = '\n';
14052     commentList[index][len + 1] = NULLCHAR;
14053   } else {
14054     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14055     char *p;
14056     commentList[index] = (char *) malloc(len + 7);
14057     safeStrCpy(commentList[index], "{\n", 3);
14058     safeStrCpy(commentList[index]+2, text, len+1);
14059     commentList[index][len+2] = NULLCHAR;
14060     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14061     strcat(commentList[index], "\n}\n");
14062   }
14063 }
14064
14065 void
14066 CrushCRs(text)
14067      char *text;
14068 {
14069   char *p = text;
14070   char *q = text;
14071   char ch;
14072
14073   do {
14074     ch = *p++;
14075     if (ch == '\r') continue;
14076     *q++ = ch;
14077   } while (ch != '\0');
14078 }
14079
14080 void
14081 AppendComment(index, text, addBraces)
14082      int index;
14083      char *text;
14084      Boolean addBraces; // [HGM] braces: tells if we should add {}
14085 {
14086     int oldlen, len;
14087     char *old;
14088
14089 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14090     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14091
14092     CrushCRs(text);
14093     while (*text == '\n') text++;
14094     len = strlen(text);
14095     while (len > 0 && text[len - 1] == '\n') len--;
14096
14097     if (len == 0) return;
14098
14099     if (commentList[index] != NULL) {
14100         old = commentList[index];
14101         oldlen = strlen(old);
14102         while(commentList[index][oldlen-1] ==  '\n')
14103           commentList[index][--oldlen] = NULLCHAR;
14104         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14105         safeStrCpy(commentList[index], old, oldlen + len + 6);
14106         free(old);
14107         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14108         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14109           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14110           while (*text == '\n') { text++; len--; }
14111           commentList[index][--oldlen] = NULLCHAR;
14112       }
14113         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14114         else          strcat(commentList[index], "\n");
14115         strcat(commentList[index], text);
14116         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14117         else          strcat(commentList[index], "\n");
14118     } else {
14119         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14120         if(addBraces)
14121           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14122         else commentList[index][0] = NULLCHAR;
14123         strcat(commentList[index], text);
14124         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14125         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14126     }
14127 }
14128
14129 static char * FindStr( char * text, char * sub_text )
14130 {
14131     char * result = strstr( text, sub_text );
14132
14133     if( result != NULL ) {
14134         result += strlen( sub_text );
14135     }
14136
14137     return result;
14138 }
14139
14140 /* [AS] Try to extract PV info from PGN comment */
14141 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14142 char *GetInfoFromComment( int index, char * text )
14143 {
14144     char * sep = text, *p;
14145
14146     if( text != NULL && index > 0 ) {
14147         int score = 0;
14148         int depth = 0;
14149         int time = -1, sec = 0, deci;
14150         char * s_eval = FindStr( text, "[%eval " );
14151         char * s_emt = FindStr( text, "[%emt " );
14152
14153         if( s_eval != NULL || s_emt != NULL ) {
14154             /* New style */
14155             char delim;
14156
14157             if( s_eval != NULL ) {
14158                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14159                     return text;
14160                 }
14161
14162                 if( delim != ']' ) {
14163                     return text;
14164                 }
14165             }
14166
14167             if( s_emt != NULL ) {
14168             }
14169                 return text;
14170         }
14171         else {
14172             /* We expect something like: [+|-]nnn.nn/dd */
14173             int score_lo = 0;
14174
14175             if(*text != '{') return text; // [HGM] braces: must be normal comment
14176
14177             sep = strchr( text, '/' );
14178             if( sep == NULL || sep < (text+4) ) {
14179                 return text;
14180             }
14181
14182             p = text;
14183             if(p[1] == '(') { // comment starts with PV
14184                p = strchr(p, ')'); // locate end of PV
14185                if(p == NULL || sep < p+5) return text;
14186                // at this point we have something like "{(.*) +0.23/6 ..."
14187                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14188                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14189                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14190             }
14191             time = -1; sec = -1; deci = -1;
14192             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14193                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14194                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14195                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14196                 return text;
14197             }
14198
14199             if( score_lo < 0 || score_lo >= 100 ) {
14200                 return text;
14201             }
14202
14203             if(sec >= 0) time = 600*time + 10*sec; else
14204             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14205
14206             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14207
14208             /* [HGM] PV time: now locate end of PV info */
14209             while( *++sep >= '0' && *sep <= '9'); // strip depth
14210             if(time >= 0)
14211             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14212             if(sec >= 0)
14213             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14214             if(deci >= 0)
14215             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14216             while(*sep == ' ') sep++;
14217         }
14218
14219         if( depth <= 0 ) {
14220             return text;
14221         }
14222
14223         if( time < 0 ) {
14224             time = -1;
14225         }
14226
14227         pvInfoList[index-1].depth = depth;
14228         pvInfoList[index-1].score = score;
14229         pvInfoList[index-1].time  = 10*time; // centi-sec
14230         if(*sep == '}') *sep = 0; else *--sep = '{';
14231         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14232     }
14233     return sep;
14234 }
14235
14236 void
14237 SendToProgram(message, cps)
14238      char *message;
14239      ChessProgramState *cps;
14240 {
14241     int count, outCount, error;
14242     char buf[MSG_SIZ];
14243
14244     if (cps->pr == NULL) return;
14245     Attention(cps);
14246
14247     if (appData.debugMode) {
14248         TimeMark now;
14249         GetTimeMark(&now);
14250         fprintf(debugFP, "%ld >%-6s: %s",
14251                 SubtractTimeMarks(&now, &programStartTime),
14252                 cps->which, message);
14253     }
14254
14255     count = strlen(message);
14256     outCount = OutputToProcess(cps->pr, message, count, &error);
14257     if (outCount < count && !exiting
14258                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14259       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14260       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14261         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14262             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14263                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14264                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14265                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14266             } else {
14267                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14268                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14269                 gameInfo.result = res;
14270             }
14271             gameInfo.resultDetails = StrSave(buf);
14272         }
14273         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14274         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14275     }
14276 }
14277
14278 void
14279 ReceiveFromProgram(isr, closure, message, count, error)
14280      InputSourceRef isr;
14281      VOIDSTAR closure;
14282      char *message;
14283      int count;
14284      int error;
14285 {
14286     char *end_str;
14287     char buf[MSG_SIZ];
14288     ChessProgramState *cps = (ChessProgramState *)closure;
14289
14290     if (isr != cps->isr) return; /* Killed intentionally */
14291     if (count <= 0) {
14292         if (count == 0) {
14293             RemoveInputSource(cps->isr);
14294             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14295                     _(cps->which), cps->program);
14296         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14297                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14298                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14299                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14300                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14301                 } else {
14302                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14303                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14304                     gameInfo.result = res;
14305                 }
14306                 gameInfo.resultDetails = StrSave(buf);
14307             }
14308             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14309             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14310         } else {
14311             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14312                     _(cps->which), cps->program);
14313             RemoveInputSource(cps->isr);
14314
14315             /* [AS] Program is misbehaving badly... kill it */
14316             if( count == -2 ) {
14317                 DestroyChildProcess( cps->pr, 9 );
14318                 cps->pr = NoProc;
14319             }
14320
14321             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14322         }
14323         return;
14324     }
14325
14326     if ((end_str = strchr(message, '\r')) != NULL)
14327       *end_str = NULLCHAR;
14328     if ((end_str = strchr(message, '\n')) != NULL)
14329       *end_str = NULLCHAR;
14330
14331     if (appData.debugMode) {
14332         TimeMark now; int print = 1;
14333         char *quote = ""; char c; int i;
14334
14335         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14336                 char start = message[0];
14337                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14338                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14339                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14340                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14341                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14342                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14343                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14344                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14345                    sscanf(message, "hint: %c", &c)!=1 && 
14346                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14347                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14348                     print = (appData.engineComments >= 2);
14349                 }
14350                 message[0] = start; // restore original message
14351         }
14352         if(print) {
14353                 GetTimeMark(&now);
14354                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14355                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14356                         quote,
14357                         message);
14358         }
14359     }
14360
14361     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14362     if (appData.icsEngineAnalyze) {
14363         if (strstr(message, "whisper") != NULL ||
14364              strstr(message, "kibitz") != NULL ||
14365             strstr(message, "tellics") != NULL) return;
14366     }
14367
14368     HandleMachineMove(message, cps);
14369 }
14370
14371
14372 void
14373 SendTimeControl(cps, mps, tc, inc, sd, st)
14374      ChessProgramState *cps;
14375      int mps, inc, sd, st;
14376      long tc;
14377 {
14378     char buf[MSG_SIZ];
14379     int seconds;
14380
14381     if( timeControl_2 > 0 ) {
14382         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14383             tc = timeControl_2;
14384         }
14385     }
14386     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14387     inc /= cps->timeOdds;
14388     st  /= cps->timeOdds;
14389
14390     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14391
14392     if (st > 0) {
14393       /* Set exact time per move, normally using st command */
14394       if (cps->stKludge) {
14395         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14396         seconds = st % 60;
14397         if (seconds == 0) {
14398           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14399         } else {
14400           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14401         }
14402       } else {
14403         snprintf(buf, MSG_SIZ, "st %d\n", st);
14404       }
14405     } else {
14406       /* Set conventional or incremental time control, using level command */
14407       if (seconds == 0) {
14408         /* Note old gnuchess bug -- minutes:seconds used to not work.
14409            Fixed in later versions, but still avoid :seconds
14410            when seconds is 0. */
14411         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14412       } else {
14413         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14414                  seconds, inc/1000.);
14415       }
14416     }
14417     SendToProgram(buf, cps);
14418
14419     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14420     /* Orthogonally, limit search to given depth */
14421     if (sd > 0) {
14422       if (cps->sdKludge) {
14423         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14424       } else {
14425         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14426       }
14427       SendToProgram(buf, cps);
14428     }
14429
14430     if(cps->nps >= 0) { /* [HGM] nps */
14431         if(cps->supportsNPS == FALSE)
14432           cps->nps = -1; // don't use if engine explicitly says not supported!
14433         else {
14434           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14435           SendToProgram(buf, cps);
14436         }
14437     }
14438 }
14439
14440 ChessProgramState *WhitePlayer()
14441 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14442 {
14443     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14444        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14445         return &second;
14446     return &first;
14447 }
14448
14449 void
14450 SendTimeRemaining(cps, machineWhite)
14451      ChessProgramState *cps;
14452      int /*boolean*/ machineWhite;
14453 {
14454     char message[MSG_SIZ];
14455     long time, otime;
14456
14457     /* Note: this routine must be called when the clocks are stopped
14458        or when they have *just* been set or switched; otherwise
14459        it will be off by the time since the current tick started.
14460     */
14461     if (machineWhite) {
14462         time = whiteTimeRemaining / 10;
14463         otime = blackTimeRemaining / 10;
14464     } else {
14465         time = blackTimeRemaining / 10;
14466         otime = whiteTimeRemaining / 10;
14467     }
14468     /* [HGM] translate opponent's time by time-odds factor */
14469     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14470     if (appData.debugMode) {
14471         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14472     }
14473
14474     if (time <= 0) time = 1;
14475     if (otime <= 0) otime = 1;
14476
14477     snprintf(message, MSG_SIZ, "time %ld\n", time);
14478     SendToProgram(message, cps);
14479
14480     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14481     SendToProgram(message, cps);
14482 }
14483
14484 int
14485 BoolFeature(p, name, loc, cps)
14486      char **p;
14487      char *name;
14488      int *loc;
14489      ChessProgramState *cps;
14490 {
14491   char buf[MSG_SIZ];
14492   int len = strlen(name);
14493   int val;
14494
14495   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14496     (*p) += len + 1;
14497     sscanf(*p, "%d", &val);
14498     *loc = (val != 0);
14499     while (**p && **p != ' ')
14500       (*p)++;
14501     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14502     SendToProgram(buf, cps);
14503     return TRUE;
14504   }
14505   return FALSE;
14506 }
14507
14508 int
14509 IntFeature(p, name, loc, cps)
14510      char **p;
14511      char *name;
14512      int *loc;
14513      ChessProgramState *cps;
14514 {
14515   char buf[MSG_SIZ];
14516   int len = strlen(name);
14517   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14518     (*p) += len + 1;
14519     sscanf(*p, "%d", loc);
14520     while (**p && **p != ' ') (*p)++;
14521     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14522     SendToProgram(buf, cps);
14523     return TRUE;
14524   }
14525   return FALSE;
14526 }
14527
14528 int
14529 StringFeature(p, name, loc, cps)
14530      char **p;
14531      char *name;
14532      char loc[];
14533      ChessProgramState *cps;
14534 {
14535   char buf[MSG_SIZ];
14536   int len = strlen(name);
14537   if (strncmp((*p), name, len) == 0
14538       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14539     (*p) += len + 2;
14540     sscanf(*p, "%[^\"]", loc);
14541     while (**p && **p != '\"') (*p)++;
14542     if (**p == '\"') (*p)++;
14543     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14544     SendToProgram(buf, cps);
14545     return TRUE;
14546   }
14547   return FALSE;
14548 }
14549
14550 int
14551 ParseOption(Option *opt, ChessProgramState *cps)
14552 // [HGM] options: process the string that defines an engine option, and determine
14553 // name, type, default value, and allowed value range
14554 {
14555         char *p, *q, buf[MSG_SIZ];
14556         int n, min = (-1)<<31, max = 1<<31, def;
14557
14558         if(p = strstr(opt->name, " -spin ")) {
14559             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14560             if(max < min) max = min; // enforce consistency
14561             if(def < min) def = min;
14562             if(def > max) def = max;
14563             opt->value = def;
14564             opt->min = min;
14565             opt->max = max;
14566             opt->type = Spin;
14567         } else if((p = strstr(opt->name, " -slider "))) {
14568             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14569             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14570             if(max < min) max = min; // enforce consistency
14571             if(def < min) def = min;
14572             if(def > max) def = max;
14573             opt->value = def;
14574             opt->min = min;
14575             opt->max = max;
14576             opt->type = Spin; // Slider;
14577         } else if((p = strstr(opt->name, " -string "))) {
14578             opt->textValue = p+9;
14579             opt->type = TextBox;
14580         } else if((p = strstr(opt->name, " -file "))) {
14581             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14582             opt->textValue = p+7;
14583             opt->type = FileName; // FileName;
14584         } else if((p = strstr(opt->name, " -path "))) {
14585             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14586             opt->textValue = p+7;
14587             opt->type = PathName; // PathName;
14588         } else if(p = strstr(opt->name, " -check ")) {
14589             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14590             opt->value = (def != 0);
14591             opt->type = CheckBox;
14592         } else if(p = strstr(opt->name, " -combo ")) {
14593             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14594             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14595             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14596             opt->value = n = 0;
14597             while(q = StrStr(q, " /// ")) {
14598                 n++; *q = 0;    // count choices, and null-terminate each of them
14599                 q += 5;
14600                 if(*q == '*') { // remember default, which is marked with * prefix
14601                     q++;
14602                     opt->value = n;
14603                 }
14604                 cps->comboList[cps->comboCnt++] = q;
14605             }
14606             cps->comboList[cps->comboCnt++] = NULL;
14607             opt->max = n + 1;
14608             opt->type = ComboBox;
14609         } else if(p = strstr(opt->name, " -button")) {
14610             opt->type = Button;
14611         } else if(p = strstr(opt->name, " -save")) {
14612             opt->type = SaveButton;
14613         } else return FALSE;
14614         *p = 0; // terminate option name
14615         // now look if the command-line options define a setting for this engine option.
14616         if(cps->optionSettings && cps->optionSettings[0])
14617             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14618         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14619           snprintf(buf, MSG_SIZ, "option %s", p);
14620                 if(p = strstr(buf, ",")) *p = 0;
14621                 if(q = strchr(buf, '=')) switch(opt->type) {
14622                     case ComboBox:
14623                         for(n=0; n<opt->max; n++)
14624                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14625                         break;
14626                     case TextBox:
14627                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14628                         break;
14629                     case Spin:
14630                     case CheckBox:
14631                         opt->value = atoi(q+1);
14632                     default:
14633                         break;
14634                 }
14635                 strcat(buf, "\n");
14636                 SendToProgram(buf, cps);
14637         }
14638         return TRUE;
14639 }
14640
14641 void
14642 FeatureDone(cps, val)
14643      ChessProgramState* cps;
14644      int val;
14645 {
14646   DelayedEventCallback cb = GetDelayedEvent();
14647   if ((cb == InitBackEnd3 && cps == &first) ||
14648       (cb == SettingsMenuIfReady && cps == &second) ||
14649       (cb == LoadEngine) ||
14650       (cb == TwoMachinesEventIfReady)) {
14651     CancelDelayedEvent();
14652     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14653   }
14654   cps->initDone = val;
14655 }
14656
14657 /* Parse feature command from engine */
14658 void
14659 ParseFeatures(args, cps)
14660      char* args;
14661      ChessProgramState *cps;
14662 {
14663   char *p = args;
14664   char *q;
14665   int val;
14666   char buf[MSG_SIZ];
14667
14668   for (;;) {
14669     while (*p == ' ') p++;
14670     if (*p == NULLCHAR) return;
14671
14672     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14673     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14674     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14675     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14676     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14677     if (BoolFeature(&p, "reuse", &val, cps)) {
14678       /* Engine can disable reuse, but can't enable it if user said no */
14679       if (!val) cps->reuse = FALSE;
14680       continue;
14681     }
14682     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14683     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14684       if (gameMode == TwoMachinesPlay) {
14685         DisplayTwoMachinesTitle();
14686       } else {
14687         DisplayTitle("");
14688       }
14689       continue;
14690     }
14691     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14692     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14693     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14694     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14695     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14696     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14697     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14698     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14699     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14700     if (IntFeature(&p, "done", &val, cps)) {
14701       FeatureDone(cps, val);
14702       continue;
14703     }
14704     /* Added by Tord: */
14705     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14706     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14707     /* End of additions by Tord */
14708
14709     /* [HGM] added features: */
14710     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14711     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14712     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14713     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14714     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14715     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14716     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14717         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14718           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14719             SendToProgram(buf, cps);
14720             continue;
14721         }
14722         if(cps->nrOptions >= MAX_OPTIONS) {
14723             cps->nrOptions--;
14724             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14725             DisplayError(buf, 0);
14726         }
14727         continue;
14728     }
14729     /* End of additions by HGM */
14730
14731     /* unknown feature: complain and skip */
14732     q = p;
14733     while (*q && *q != '=') q++;
14734     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14735     SendToProgram(buf, cps);
14736     p = q;
14737     if (*p == '=') {
14738       p++;
14739       if (*p == '\"') {
14740         p++;
14741         while (*p && *p != '\"') p++;
14742         if (*p == '\"') p++;
14743       } else {
14744         while (*p && *p != ' ') p++;
14745       }
14746     }
14747   }
14748
14749 }
14750
14751 void
14752 PeriodicUpdatesEvent(newState)
14753      int newState;
14754 {
14755     if (newState == appData.periodicUpdates)
14756       return;
14757
14758     appData.periodicUpdates=newState;
14759
14760     /* Display type changes, so update it now */
14761 //    DisplayAnalysis();
14762
14763     /* Get the ball rolling again... */
14764     if (newState) {
14765         AnalysisPeriodicEvent(1);
14766         StartAnalysisClock();
14767     }
14768 }
14769
14770 void
14771 PonderNextMoveEvent(newState)
14772      int newState;
14773 {
14774     if (newState == appData.ponderNextMove) return;
14775     if (gameMode == EditPosition) EditPositionDone(TRUE);
14776     if (newState) {
14777         SendToProgram("hard\n", &first);
14778         if (gameMode == TwoMachinesPlay) {
14779             SendToProgram("hard\n", &second);
14780         }
14781     } else {
14782         SendToProgram("easy\n", &first);
14783         thinkOutput[0] = NULLCHAR;
14784         if (gameMode == TwoMachinesPlay) {
14785             SendToProgram("easy\n", &second);
14786         }
14787     }
14788     appData.ponderNextMove = newState;
14789 }
14790
14791 void
14792 NewSettingEvent(option, feature, command, value)
14793      char *command;
14794      int option, value, *feature;
14795 {
14796     char buf[MSG_SIZ];
14797
14798     if (gameMode == EditPosition) EditPositionDone(TRUE);
14799     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14800     if(feature == NULL || *feature) SendToProgram(buf, &first);
14801     if (gameMode == TwoMachinesPlay) {
14802         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14803     }
14804 }
14805
14806 void
14807 ShowThinkingEvent()
14808 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14809 {
14810     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14811     int newState = appData.showThinking
14812         // [HGM] thinking: other features now need thinking output as well
14813         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14814
14815     if (oldState == newState) return;
14816     oldState = newState;
14817     if (gameMode == EditPosition) EditPositionDone(TRUE);
14818     if (oldState) {
14819         SendToProgram("post\n", &first);
14820         if (gameMode == TwoMachinesPlay) {
14821             SendToProgram("post\n", &second);
14822         }
14823     } else {
14824         SendToProgram("nopost\n", &first);
14825         thinkOutput[0] = NULLCHAR;
14826         if (gameMode == TwoMachinesPlay) {
14827             SendToProgram("nopost\n", &second);
14828         }
14829     }
14830 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14831 }
14832
14833 void
14834 AskQuestionEvent(title, question, replyPrefix, which)
14835      char *title; char *question; char *replyPrefix; char *which;
14836 {
14837   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14838   if (pr == NoProc) return;
14839   AskQuestion(title, question, replyPrefix, pr);
14840 }
14841
14842 void
14843 TypeInEvent(char firstChar)
14844 {
14845     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14846         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14847         gameMode == AnalyzeMode || gameMode == EditGame || \r
14848         gameMode == EditPosition || gameMode == IcsExamining ||\r
14849         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14850         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14851                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14852                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14853         gameMode == Training) PopUpMoveDialog(firstChar);
14854 }
14855
14856 void
14857 TypeInDoneEvent(char *move)
14858 {
14859         Board board;
14860         int n, fromX, fromY, toX, toY;
14861         char promoChar;
14862         ChessMove moveType;\r
14863
14864         // [HGM] FENedit\r
14865         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14866                 EditPositionPasteFEN(move);\r
14867                 return;\r
14868         }\r
14869         // [HGM] movenum: allow move number to be typed in any mode\r
14870         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14871           ToNrEvent(2*n-1);\r
14872           return;\r
14873         }\r
14874
14875       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14876         gameMode != Training) {\r
14877         DisplayMoveError(_("Displayed move is not current"));\r
14878       } else {\r
14879         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14880           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14881         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14882         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14883           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14884           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14885         } else {\r
14886           DisplayMoveError(_("Could not parse move"));\r
14887         }
14888       }\r
14889 }\r
14890
14891 void
14892 DisplayMove(moveNumber)
14893      int moveNumber;
14894 {
14895     char message[MSG_SIZ];
14896     char res[MSG_SIZ];
14897     char cpThinkOutput[MSG_SIZ];
14898
14899     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14900
14901     if (moveNumber == forwardMostMove - 1 ||
14902         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14903
14904         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14905
14906         if (strchr(cpThinkOutput, '\n')) {
14907             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14908         }
14909     } else {
14910         *cpThinkOutput = NULLCHAR;
14911     }
14912
14913     /* [AS] Hide thinking from human user */
14914     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14915         *cpThinkOutput = NULLCHAR;
14916         if( thinkOutput[0] != NULLCHAR ) {
14917             int i;
14918
14919             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14920                 cpThinkOutput[i] = '.';
14921             }
14922             cpThinkOutput[i] = NULLCHAR;
14923             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14924         }
14925     }
14926
14927     if (moveNumber == forwardMostMove - 1 &&
14928         gameInfo.resultDetails != NULL) {
14929         if (gameInfo.resultDetails[0] == NULLCHAR) {
14930           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14931         } else {
14932           snprintf(res, MSG_SIZ, " {%s} %s",
14933                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14934         }
14935     } else {
14936         res[0] = NULLCHAR;
14937     }
14938
14939     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14940         DisplayMessage(res, cpThinkOutput);
14941     } else {
14942       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14943                 WhiteOnMove(moveNumber) ? " " : ".. ",
14944                 parseList[moveNumber], res);
14945         DisplayMessage(message, cpThinkOutput);
14946     }
14947 }
14948
14949 void
14950 DisplayComment(moveNumber, text)
14951      int moveNumber;
14952      char *text;
14953 {
14954     char title[MSG_SIZ];
14955     char buf[8000]; // comment can be long!
14956     int score, depth;
14957
14958     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14959       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14960     } else {
14961       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14962               WhiteOnMove(moveNumber) ? " " : ".. ",
14963               parseList[moveNumber]);
14964     }
14965     // [HGM] PV info: display PV info together with (or as) comment
14966     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14967       if(text == NULL) text = "";
14968       score = pvInfoList[moveNumber].score;
14969       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14970               depth, (pvInfoList[moveNumber].time+50)/100, text);
14971       text = buf;
14972     }
14973     if (text != NULL && (appData.autoDisplayComment || commentUp))
14974         CommentPopUp(title, text);
14975 }
14976
14977 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14978  * might be busy thinking or pondering.  It can be omitted if your
14979  * gnuchess is configured to stop thinking immediately on any user
14980  * input.  However, that gnuchess feature depends on the FIONREAD
14981  * ioctl, which does not work properly on some flavors of Unix.
14982  */
14983 void
14984 Attention(cps)
14985      ChessProgramState *cps;
14986 {
14987 #if ATTENTION
14988     if (!cps->useSigint) return;
14989     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14990     switch (gameMode) {
14991       case MachinePlaysWhite:
14992       case MachinePlaysBlack:
14993       case TwoMachinesPlay:
14994       case IcsPlayingWhite:
14995       case IcsPlayingBlack:
14996       case AnalyzeMode:
14997       case AnalyzeFile:
14998         /* Skip if we know it isn't thinking */
14999         if (!cps->maybeThinking) return;
15000         if (appData.debugMode)
15001           fprintf(debugFP, "Interrupting %s\n", cps->which);
15002         InterruptChildProcess(cps->pr);
15003         cps->maybeThinking = FALSE;
15004         break;
15005       default:
15006         break;
15007     }
15008 #endif /*ATTENTION*/
15009 }
15010
15011 int
15012 CheckFlags()
15013 {
15014     if (whiteTimeRemaining <= 0) {
15015         if (!whiteFlag) {
15016             whiteFlag = TRUE;
15017             if (appData.icsActive) {
15018                 if (appData.autoCallFlag &&
15019                     gameMode == IcsPlayingBlack && !blackFlag) {
15020                   SendToICS(ics_prefix);
15021                   SendToICS("flag\n");
15022                 }
15023             } else {
15024                 if (blackFlag) {
15025                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15026                 } else {
15027                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15028                     if (appData.autoCallFlag) {
15029                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15030                         return TRUE;
15031                     }
15032                 }
15033             }
15034         }
15035     }
15036     if (blackTimeRemaining <= 0) {
15037         if (!blackFlag) {
15038             blackFlag = TRUE;
15039             if (appData.icsActive) {
15040                 if (appData.autoCallFlag &&
15041                     gameMode == IcsPlayingWhite && !whiteFlag) {
15042                   SendToICS(ics_prefix);
15043                   SendToICS("flag\n");
15044                 }
15045             } else {
15046                 if (whiteFlag) {
15047                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15048                 } else {
15049                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15050                     if (appData.autoCallFlag) {
15051                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15052                         return TRUE;
15053                     }
15054                 }
15055             }
15056         }
15057     }
15058     return FALSE;
15059 }
15060
15061 void
15062 CheckTimeControl()
15063 {
15064     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15065         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15066
15067     /*
15068      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15069      */
15070     if ( !WhiteOnMove(forwardMostMove) ) {
15071         /* White made time control */
15072         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15073         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15074         /* [HGM] time odds: correct new time quota for time odds! */
15075                                             / WhitePlayer()->timeOdds;
15076         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15077     } else {
15078         lastBlack -= blackTimeRemaining;
15079         /* Black made time control */
15080         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15081                                             / WhitePlayer()->other->timeOdds;
15082         lastWhite = whiteTimeRemaining;
15083     }
15084 }
15085
15086 void
15087 DisplayBothClocks()
15088 {
15089     int wom = gameMode == EditPosition ?
15090       !blackPlaysFirst : WhiteOnMove(currentMove);
15091     DisplayWhiteClock(whiteTimeRemaining, wom);
15092     DisplayBlackClock(blackTimeRemaining, !wom);
15093 }
15094
15095
15096 /* Timekeeping seems to be a portability nightmare.  I think everyone
15097    has ftime(), but I'm really not sure, so I'm including some ifdefs
15098    to use other calls if you don't.  Clocks will be less accurate if
15099    you have neither ftime nor gettimeofday.
15100 */
15101
15102 /* VS 2008 requires the #include outside of the function */
15103 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15104 #include <sys/timeb.h>
15105 #endif
15106
15107 /* Get the current time as a TimeMark */
15108 void
15109 GetTimeMark(tm)
15110      TimeMark *tm;
15111 {
15112 #if HAVE_GETTIMEOFDAY
15113
15114     struct timeval timeVal;
15115     struct timezone timeZone;
15116
15117     gettimeofday(&timeVal, &timeZone);
15118     tm->sec = (long) timeVal.tv_sec;
15119     tm->ms = (int) (timeVal.tv_usec / 1000L);
15120
15121 #else /*!HAVE_GETTIMEOFDAY*/
15122 #if HAVE_FTIME
15123
15124 // include <sys/timeb.h> / moved to just above start of function
15125     struct timeb timeB;
15126
15127     ftime(&timeB);
15128     tm->sec = (long) timeB.time;
15129     tm->ms = (int) timeB.millitm;
15130
15131 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15132     tm->sec = (long) time(NULL);
15133     tm->ms = 0;
15134 #endif
15135 #endif
15136 }
15137
15138 /* Return the difference in milliseconds between two
15139    time marks.  We assume the difference will fit in a long!
15140 */
15141 long
15142 SubtractTimeMarks(tm2, tm1)
15143      TimeMark *tm2, *tm1;
15144 {
15145     return 1000L*(tm2->sec - tm1->sec) +
15146            (long) (tm2->ms - tm1->ms);
15147 }
15148
15149
15150 /*
15151  * Code to manage the game clocks.
15152  *
15153  * In tournament play, black starts the clock and then white makes a move.
15154  * We give the human user a slight advantage if he is playing white---the
15155  * clocks don't run until he makes his first move, so it takes zero time.
15156  * Also, we don't account for network lag, so we could get out of sync
15157  * with GNU Chess's clock -- but then, referees are always right.
15158  */
15159
15160 static TimeMark tickStartTM;
15161 static long intendedTickLength;
15162
15163 long
15164 NextTickLength(timeRemaining)
15165      long timeRemaining;
15166 {
15167     long nominalTickLength, nextTickLength;
15168
15169     if (timeRemaining > 0L && timeRemaining <= 10000L)
15170       nominalTickLength = 100L;
15171     else
15172       nominalTickLength = 1000L;
15173     nextTickLength = timeRemaining % nominalTickLength;
15174     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15175
15176     return nextTickLength;
15177 }
15178
15179 /* Adjust clock one minute up or down */
15180 void
15181 AdjustClock(Boolean which, int dir)
15182 {
15183     if(which) blackTimeRemaining += 60000*dir;
15184     else      whiteTimeRemaining += 60000*dir;
15185     DisplayBothClocks();
15186 }
15187
15188 /* Stop clocks and reset to a fresh time control */
15189 void
15190 ResetClocks()
15191 {
15192     (void) StopClockTimer();
15193     if (appData.icsActive) {
15194         whiteTimeRemaining = blackTimeRemaining = 0;
15195     } else if (searchTime) {
15196         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15197         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15198     } else { /* [HGM] correct new time quote for time odds */
15199         whiteTC = blackTC = fullTimeControlString;
15200         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15201         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15202     }
15203     if (whiteFlag || blackFlag) {
15204         DisplayTitle("");
15205         whiteFlag = blackFlag = FALSE;
15206     }
15207     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15208     DisplayBothClocks();
15209 }
15210
15211 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15212
15213 /* Decrement running clock by amount of time that has passed */
15214 void
15215 DecrementClocks()
15216 {
15217     long timeRemaining;
15218     long lastTickLength, fudge;
15219     TimeMark now;
15220
15221     if (!appData.clockMode) return;
15222     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15223
15224     GetTimeMark(&now);
15225
15226     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15227
15228     /* Fudge if we woke up a little too soon */
15229     fudge = intendedTickLength - lastTickLength;
15230     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15231
15232     if (WhiteOnMove(forwardMostMove)) {
15233         if(whiteNPS >= 0) lastTickLength = 0;
15234         timeRemaining = whiteTimeRemaining -= lastTickLength;
15235         if(timeRemaining < 0 && !appData.icsActive) {
15236             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15237             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15238                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15239                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15240             }
15241         }
15242         DisplayWhiteClock(whiteTimeRemaining - fudge,
15243                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15244     } else {
15245         if(blackNPS >= 0) lastTickLength = 0;
15246         timeRemaining = blackTimeRemaining -= lastTickLength;
15247         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15248             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15249             if(suddenDeath) {
15250                 blackStartMove = forwardMostMove;
15251                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15252             }
15253         }
15254         DisplayBlackClock(blackTimeRemaining - fudge,
15255                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15256     }
15257     if (CheckFlags()) return;
15258
15259     tickStartTM = now;
15260     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15261     StartClockTimer(intendedTickLength);
15262
15263     /* if the time remaining has fallen below the alarm threshold, sound the
15264      * alarm. if the alarm has sounded and (due to a takeback or time control
15265      * with increment) the time remaining has increased to a level above the
15266      * threshold, reset the alarm so it can sound again.
15267      */
15268
15269     if (appData.icsActive && appData.icsAlarm) {
15270
15271         /* make sure we are dealing with the user's clock */
15272         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15273                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15274            )) return;
15275
15276         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15277             alarmSounded = FALSE;
15278         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15279             PlayAlarmSound();
15280             alarmSounded = TRUE;
15281         }
15282     }
15283 }
15284
15285
15286 /* A player has just moved, so stop the previously running
15287    clock and (if in clock mode) start the other one.
15288    We redisplay both clocks in case we're in ICS mode, because
15289    ICS gives us an update to both clocks after every move.
15290    Note that this routine is called *after* forwardMostMove
15291    is updated, so the last fractional tick must be subtracted
15292    from the color that is *not* on move now.
15293 */
15294 void
15295 SwitchClocks(int newMoveNr)
15296 {
15297     long lastTickLength;
15298     TimeMark now;
15299     int flagged = FALSE;
15300
15301     GetTimeMark(&now);
15302
15303     if (StopClockTimer() && appData.clockMode) {
15304         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15305         if (!WhiteOnMove(forwardMostMove)) {
15306             if(blackNPS >= 0) lastTickLength = 0;
15307             blackTimeRemaining -= lastTickLength;
15308            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15309 //         if(pvInfoList[forwardMostMove].time == -1)
15310                  pvInfoList[forwardMostMove].time =               // use GUI time
15311                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15312         } else {
15313            if(whiteNPS >= 0) lastTickLength = 0;
15314            whiteTimeRemaining -= lastTickLength;
15315            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15316 //         if(pvInfoList[forwardMostMove].time == -1)
15317                  pvInfoList[forwardMostMove].time =
15318                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15319         }
15320         flagged = CheckFlags();
15321     }
15322     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15323     CheckTimeControl();
15324
15325     if (flagged || !appData.clockMode) return;
15326
15327     switch (gameMode) {
15328       case MachinePlaysBlack:
15329       case MachinePlaysWhite:
15330       case BeginningOfGame:
15331         if (pausing) return;
15332         break;
15333
15334       case EditGame:
15335       case PlayFromGameFile:
15336       case IcsExamining:
15337         return;
15338
15339       default:
15340         break;
15341     }
15342
15343     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15344         if(WhiteOnMove(forwardMostMove))
15345              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15346         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15347     }
15348
15349     tickStartTM = now;
15350     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15351       whiteTimeRemaining : blackTimeRemaining);
15352     StartClockTimer(intendedTickLength);
15353 }
15354
15355
15356 /* Stop both clocks */
15357 void
15358 StopClocks()
15359 {
15360     long lastTickLength;
15361     TimeMark now;
15362
15363     if (!StopClockTimer()) return;
15364     if (!appData.clockMode) return;
15365
15366     GetTimeMark(&now);
15367
15368     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15369     if (WhiteOnMove(forwardMostMove)) {
15370         if(whiteNPS >= 0) lastTickLength = 0;
15371         whiteTimeRemaining -= lastTickLength;
15372         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15373     } else {
15374         if(blackNPS >= 0) lastTickLength = 0;
15375         blackTimeRemaining -= lastTickLength;
15376         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15377     }
15378     CheckFlags();
15379 }
15380
15381 /* Start clock of player on move.  Time may have been reset, so
15382    if clock is already running, stop and restart it. */
15383 void
15384 StartClocks()
15385 {
15386     (void) StopClockTimer(); /* in case it was running already */
15387     DisplayBothClocks();
15388     if (CheckFlags()) return;
15389
15390     if (!appData.clockMode) return;
15391     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15392
15393     GetTimeMark(&tickStartTM);
15394     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15395       whiteTimeRemaining : blackTimeRemaining);
15396
15397    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15398     whiteNPS = blackNPS = -1;
15399     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15400        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15401         whiteNPS = first.nps;
15402     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15403        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15404         blackNPS = first.nps;
15405     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15406         whiteNPS = second.nps;
15407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15408         blackNPS = second.nps;
15409     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15410
15411     StartClockTimer(intendedTickLength);
15412 }
15413
15414 char *
15415 TimeString(ms)
15416      long ms;
15417 {
15418     long second, minute, hour, day;
15419     char *sign = "";
15420     static char buf[32];
15421
15422     if (ms > 0 && ms <= 9900) {
15423       /* convert milliseconds to tenths, rounding up */
15424       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15425
15426       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15427       return buf;
15428     }
15429
15430     /* convert milliseconds to seconds, rounding up */
15431     /* use floating point to avoid strangeness of integer division
15432        with negative dividends on many machines */
15433     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15434
15435     if (second < 0) {
15436         sign = "-";
15437         second = -second;
15438     }
15439
15440     day = second / (60 * 60 * 24);
15441     second = second % (60 * 60 * 24);
15442     hour = second / (60 * 60);
15443     second = second % (60 * 60);
15444     minute = second / 60;
15445     second = second % 60;
15446
15447     if (day > 0)
15448       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15449               sign, day, hour, minute, second);
15450     else if (hour > 0)
15451       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15452     else
15453       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15454
15455     return buf;
15456 }
15457
15458
15459 /*
15460  * This is necessary because some C libraries aren't ANSI C compliant yet.
15461  */
15462 char *
15463 StrStr(string, match)
15464      char *string, *match;
15465 {
15466     int i, length;
15467
15468     length = strlen(match);
15469
15470     for (i = strlen(string) - length; i >= 0; i--, string++)
15471       if (!strncmp(match, string, length))
15472         return string;
15473
15474     return NULL;
15475 }
15476
15477 char *
15478 StrCaseStr(string, match)
15479      char *string, *match;
15480 {
15481     int i, j, length;
15482
15483     length = strlen(match);
15484
15485     for (i = strlen(string) - length; i >= 0; i--, string++) {
15486         for (j = 0; j < length; j++) {
15487             if (ToLower(match[j]) != ToLower(string[j]))
15488               break;
15489         }
15490         if (j == length) return string;
15491     }
15492
15493     return NULL;
15494 }
15495
15496 #ifndef _amigados
15497 int
15498 StrCaseCmp(s1, s2)
15499      char *s1, *s2;
15500 {
15501     char c1, c2;
15502
15503     for (;;) {
15504         c1 = ToLower(*s1++);
15505         c2 = ToLower(*s2++);
15506         if (c1 > c2) return 1;
15507         if (c1 < c2) return -1;
15508         if (c1 == NULLCHAR) return 0;
15509     }
15510 }
15511
15512
15513 int
15514 ToLower(c)
15515      int c;
15516 {
15517     return isupper(c) ? tolower(c) : c;
15518 }
15519
15520
15521 int
15522 ToUpper(c)
15523      int c;
15524 {
15525     return islower(c) ? toupper(c) : c;
15526 }
15527 #endif /* !_amigados    */
15528
15529 char *
15530 StrSave(s)
15531      char *s;
15532 {
15533   char *ret;
15534
15535   if ((ret = (char *) malloc(strlen(s) + 1)))
15536     {
15537       safeStrCpy(ret, s, strlen(s)+1);
15538     }
15539   return ret;
15540 }
15541
15542 char *
15543 StrSavePtr(s, savePtr)
15544      char *s, **savePtr;
15545 {
15546     if (*savePtr) {
15547         free(*savePtr);
15548     }
15549     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15550       safeStrCpy(*savePtr, s, strlen(s)+1);
15551     }
15552     return(*savePtr);
15553 }
15554
15555 char *
15556 PGNDate()
15557 {
15558     time_t clock;
15559     struct tm *tm;
15560     char buf[MSG_SIZ];
15561
15562     clock = time((time_t *)NULL);
15563     tm = localtime(&clock);
15564     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15565             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15566     return StrSave(buf);
15567 }
15568
15569
15570 char *
15571 PositionToFEN(move, overrideCastling)
15572      int move;
15573      char *overrideCastling;
15574 {
15575     int i, j, fromX, fromY, toX, toY;
15576     int whiteToPlay;
15577     char buf[128];
15578     char *p, *q;
15579     int emptycount;
15580     ChessSquare piece;
15581
15582     whiteToPlay = (gameMode == EditPosition) ?
15583       !blackPlaysFirst : (move % 2 == 0);
15584     p = buf;
15585
15586     /* Piece placement data */
15587     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15588         emptycount = 0;
15589         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15590             if (boards[move][i][j] == EmptySquare) {
15591                 emptycount++;
15592             } else { ChessSquare piece = boards[move][i][j];
15593                 if (emptycount > 0) {
15594                     if(emptycount<10) /* [HGM] can be >= 10 */
15595                         *p++ = '0' + emptycount;
15596                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15597                     emptycount = 0;
15598                 }
15599                 if(PieceToChar(piece) == '+') {
15600                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15601                     *p++ = '+';
15602                     piece = (ChessSquare)(DEMOTED piece);
15603                 }
15604                 *p++ = PieceToChar(piece);
15605                 if(p[-1] == '~') {
15606                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15607                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15608                     *p++ = '~';
15609                 }
15610             }
15611         }
15612         if (emptycount > 0) {
15613             if(emptycount<10) /* [HGM] can be >= 10 */
15614                 *p++ = '0' + emptycount;
15615             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15616             emptycount = 0;
15617         }
15618         *p++ = '/';
15619     }
15620     *(p - 1) = ' ';
15621
15622     /* [HGM] print Crazyhouse or Shogi holdings */
15623     if( gameInfo.holdingsWidth ) {
15624         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15625         q = p;
15626         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15627             piece = boards[move][i][BOARD_WIDTH-1];
15628             if( piece != EmptySquare )
15629               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15630                   *p++ = PieceToChar(piece);
15631         }
15632         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15633             piece = boards[move][BOARD_HEIGHT-i-1][0];
15634             if( piece != EmptySquare )
15635               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15636                   *p++ = PieceToChar(piece);
15637         }
15638
15639         if( q == p ) *p++ = '-';
15640         *p++ = ']';
15641         *p++ = ' ';
15642     }
15643
15644     /* Active color */
15645     *p++ = whiteToPlay ? 'w' : 'b';
15646     *p++ = ' ';
15647
15648   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15649     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15650   } else {
15651   if(nrCastlingRights) {
15652      q = p;
15653      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15654        /* [HGM] write directly from rights */
15655            if(boards[move][CASTLING][2] != NoRights &&
15656               boards[move][CASTLING][0] != NoRights   )
15657                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15658            if(boards[move][CASTLING][2] != NoRights &&
15659               boards[move][CASTLING][1] != NoRights   )
15660                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15661            if(boards[move][CASTLING][5] != NoRights &&
15662               boards[move][CASTLING][3] != NoRights   )
15663                 *p++ = boards[move][CASTLING][3] + AAA;
15664            if(boards[move][CASTLING][5] != NoRights &&
15665               boards[move][CASTLING][4] != NoRights   )
15666                 *p++ = boards[move][CASTLING][4] + AAA;
15667      } else {
15668
15669         /* [HGM] write true castling rights */
15670         if( nrCastlingRights == 6 ) {
15671             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15672                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15673             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15674                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15675             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15676                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15677             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15678                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15679         }
15680      }
15681      if (q == p) *p++ = '-'; /* No castling rights */
15682      *p++ = ' ';
15683   }
15684
15685   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15686      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15687     /* En passant target square */
15688     if (move > backwardMostMove) {
15689         fromX = moveList[move - 1][0] - AAA;
15690         fromY = moveList[move - 1][1] - ONE;
15691         toX = moveList[move - 1][2] - AAA;
15692         toY = moveList[move - 1][3] - ONE;
15693         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15694             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15695             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15696             fromX == toX) {
15697             /* 2-square pawn move just happened */
15698             *p++ = toX + AAA;
15699             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15700         } else {
15701             *p++ = '-';
15702         }
15703     } else if(move == backwardMostMove) {
15704         // [HGM] perhaps we should always do it like this, and forget the above?
15705         if((signed char)boards[move][EP_STATUS] >= 0) {
15706             *p++ = boards[move][EP_STATUS] + AAA;
15707             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15708         } else {
15709             *p++ = '-';
15710         }
15711     } else {
15712         *p++ = '-';
15713     }
15714     *p++ = ' ';
15715   }
15716   }
15717
15718     /* [HGM] find reversible plies */
15719     {   int i = 0, j=move;
15720
15721         if (appData.debugMode) { int k;
15722             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15723             for(k=backwardMostMove; k<=forwardMostMove; k++)
15724                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15725
15726         }
15727
15728         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15729         if( j == backwardMostMove ) i += initialRulePlies;
15730         sprintf(p, "%d ", i);
15731         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15732     }
15733     /* Fullmove number */
15734     sprintf(p, "%d", (move / 2) + 1);
15735
15736     return StrSave(buf);
15737 }
15738
15739 Boolean
15740 ParseFEN(board, blackPlaysFirst, fen)
15741     Board board;
15742      int *blackPlaysFirst;
15743      char *fen;
15744 {
15745     int i, j;
15746     char *p, c;
15747     int emptycount;
15748     ChessSquare piece;
15749
15750     p = fen;
15751
15752     /* [HGM] by default clear Crazyhouse holdings, if present */
15753     if(gameInfo.holdingsWidth) {
15754        for(i=0; i<BOARD_HEIGHT; i++) {
15755            board[i][0]             = EmptySquare; /* black holdings */
15756            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15757            board[i][1]             = (ChessSquare) 0; /* black counts */
15758            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15759        }
15760     }
15761
15762     /* Piece placement data */
15763     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15764         j = 0;
15765         for (;;) {
15766             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15767                 if (*p == '/') p++;
15768                 emptycount = gameInfo.boardWidth - j;
15769                 while (emptycount--)
15770                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15771                 break;
15772 #if(BOARD_FILES >= 10)
15773             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15774                 p++; emptycount=10;
15775                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15776                 while (emptycount--)
15777                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15778 #endif
15779             } else if (isdigit(*p)) {
15780                 emptycount = *p++ - '0';
15781                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15782                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15783                 while (emptycount--)
15784                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15785             } else if (*p == '+' || isalpha(*p)) {
15786                 if (j >= gameInfo.boardWidth) return FALSE;
15787                 if(*p=='+') {
15788                     piece = CharToPiece(*++p);
15789                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15790                     piece = (ChessSquare) (PROMOTED piece ); p++;
15791                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15792                 } else piece = CharToPiece(*p++);
15793
15794                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15795                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15796                     piece = (ChessSquare) (PROMOTED piece);
15797                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15798                     p++;
15799                 }
15800                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15801             } else {
15802                 return FALSE;
15803             }
15804         }
15805     }
15806     while (*p == '/' || *p == ' ') p++;
15807
15808     /* [HGM] look for Crazyhouse holdings here */
15809     while(*p==' ') p++;
15810     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15811         if(*p == '[') p++;
15812         if(*p == '-' ) p++; /* empty holdings */ else {
15813             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15814             /* if we would allow FEN reading to set board size, we would   */
15815             /* have to add holdings and shift the board read so far here   */
15816             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15817                 p++;
15818                 if((int) piece >= (int) BlackPawn ) {
15819                     i = (int)piece - (int)BlackPawn;
15820                     i = PieceToNumber((ChessSquare)i);
15821                     if( i >= gameInfo.holdingsSize ) return FALSE;
15822                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15823                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15824                 } else {
15825                     i = (int)piece - (int)WhitePawn;
15826                     i = PieceToNumber((ChessSquare)i);
15827                     if( i >= gameInfo.holdingsSize ) return FALSE;
15828                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15829                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15830                 }
15831             }
15832         }
15833         if(*p == ']') p++;
15834     }
15835
15836     while(*p == ' ') p++;
15837
15838     /* Active color */
15839     c = *p++;
15840     if(appData.colorNickNames) {
15841       if( c == appData.colorNickNames[0] ) c = 'w'; else
15842       if( c == appData.colorNickNames[1] ) c = 'b';
15843     }
15844     switch (c) {
15845       case 'w':
15846         *blackPlaysFirst = FALSE;
15847         break;
15848       case 'b':
15849         *blackPlaysFirst = TRUE;
15850         break;
15851       default:
15852         return FALSE;
15853     }
15854
15855     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15856     /* return the extra info in global variiables             */
15857
15858     /* set defaults in case FEN is incomplete */
15859     board[EP_STATUS] = EP_UNKNOWN;
15860     for(i=0; i<nrCastlingRights; i++ ) {
15861         board[CASTLING][i] =
15862             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15863     }   /* assume possible unless obviously impossible */
15864     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15865     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15866     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15867                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15868     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15869     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15870     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15871                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15872     FENrulePlies = 0;
15873
15874     while(*p==' ') p++;
15875     if(nrCastlingRights) {
15876       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15877           /* castling indicator present, so default becomes no castlings */
15878           for(i=0; i<nrCastlingRights; i++ ) {
15879                  board[CASTLING][i] = NoRights;
15880           }
15881       }
15882       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15883              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15884              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15885              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15886         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15887
15888         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15889             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15890             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15891         }
15892         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15893             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15894         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15895                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15896         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15897                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15898         switch(c) {
15899           case'K':
15900               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15901               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15902               board[CASTLING][2] = whiteKingFile;
15903               break;
15904           case'Q':
15905               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15906               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15907               board[CASTLING][2] = whiteKingFile;
15908               break;
15909           case'k':
15910               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15911               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15912               board[CASTLING][5] = blackKingFile;
15913               break;
15914           case'q':
15915               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15916               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15917               board[CASTLING][5] = blackKingFile;
15918           case '-':
15919               break;
15920           default: /* FRC castlings */
15921               if(c >= 'a') { /* black rights */
15922                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15923                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15924                   if(i == BOARD_RGHT) break;
15925                   board[CASTLING][5] = i;
15926                   c -= AAA;
15927                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15928                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15929                   if(c > i)
15930                       board[CASTLING][3] = c;
15931                   else
15932                       board[CASTLING][4] = c;
15933               } else { /* white rights */
15934                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15935                     if(board[0][i] == WhiteKing) break;
15936                   if(i == BOARD_RGHT) break;
15937                   board[CASTLING][2] = i;
15938                   c -= AAA - 'a' + 'A';
15939                   if(board[0][c] >= WhiteKing) break;
15940                   if(c > i)
15941                       board[CASTLING][0] = c;
15942                   else
15943                       board[CASTLING][1] = c;
15944               }
15945         }
15946       }
15947       for(i=0; i<nrCastlingRights; i++)
15948         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15949     if (appData.debugMode) {
15950         fprintf(debugFP, "FEN castling rights:");
15951         for(i=0; i<nrCastlingRights; i++)
15952         fprintf(debugFP, " %d", board[CASTLING][i]);
15953         fprintf(debugFP, "\n");
15954     }
15955
15956       while(*p==' ') p++;
15957     }
15958
15959     /* read e.p. field in games that know e.p. capture */
15960     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15961        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15962       if(*p=='-') {
15963         p++; board[EP_STATUS] = EP_NONE;
15964       } else {
15965          char c = *p++ - AAA;
15966
15967          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15968          if(*p >= '0' && *p <='9') p++;
15969          board[EP_STATUS] = c;
15970       }
15971     }
15972
15973
15974     if(sscanf(p, "%d", &i) == 1) {
15975         FENrulePlies = i; /* 50-move ply counter */
15976         /* (The move number is still ignored)    */
15977     }
15978
15979     return TRUE;
15980 }
15981
15982 void
15983 EditPositionPasteFEN(char *fen)
15984 {
15985   if (fen != NULL) {
15986     Board initial_position;
15987
15988     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15989       DisplayError(_("Bad FEN position in clipboard"), 0);
15990       return ;
15991     } else {
15992       int savedBlackPlaysFirst = blackPlaysFirst;
15993       EditPositionEvent();
15994       blackPlaysFirst = savedBlackPlaysFirst;
15995       CopyBoard(boards[0], initial_position);
15996       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15997       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15998       DisplayBothClocks();
15999       DrawPosition(FALSE, boards[currentMove]);
16000     }
16001   }
16002 }
16003
16004 static char cseq[12] = "\\   ";
16005
16006 Boolean set_cont_sequence(char *new_seq)
16007 {
16008     int len;
16009     Boolean ret;
16010
16011     // handle bad attempts to set the sequence
16012         if (!new_seq)
16013                 return 0; // acceptable error - no debug
16014
16015     len = strlen(new_seq);
16016     ret = (len > 0) && (len < sizeof(cseq));
16017     if (ret)
16018       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16019     else if (appData.debugMode)
16020       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16021     return ret;
16022 }
16023
16024 /*
16025     reformat a source message so words don't cross the width boundary.  internal
16026     newlines are not removed.  returns the wrapped size (no null character unless
16027     included in source message).  If dest is NULL, only calculate the size required
16028     for the dest buffer.  lp argument indicats line position upon entry, and it's
16029     passed back upon exit.
16030 */
16031 int wrap(char *dest, char *src, int count, int width, int *lp)
16032 {
16033     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16034
16035     cseq_len = strlen(cseq);
16036     old_line = line = *lp;
16037     ansi = len = clen = 0;
16038
16039     for (i=0; i < count; i++)
16040     {
16041         if (src[i] == '\033')
16042             ansi = 1;
16043
16044         // if we hit the width, back up
16045         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16046         {
16047             // store i & len in case the word is too long
16048             old_i = i, old_len = len;
16049
16050             // find the end of the last word
16051             while (i && src[i] != ' ' && src[i] != '\n')
16052             {
16053                 i--;
16054                 len--;
16055             }
16056
16057             // word too long?  restore i & len before splitting it
16058             if ((old_i-i+clen) >= width)
16059             {
16060                 i = old_i;
16061                 len = old_len;
16062             }
16063
16064             // extra space?
16065             if (i && src[i-1] == ' ')
16066                 len--;
16067
16068             if (src[i] != ' ' && src[i] != '\n')
16069             {
16070                 i--;
16071                 if (len)
16072                     len--;
16073             }
16074
16075             // now append the newline and continuation sequence
16076             if (dest)
16077                 dest[len] = '\n';
16078             len++;
16079             if (dest)
16080                 strncpy(dest+len, cseq, cseq_len);
16081             len += cseq_len;
16082             line = cseq_len;
16083             clen = cseq_len;
16084             continue;
16085         }
16086
16087         if (dest)
16088             dest[len] = src[i];
16089         len++;
16090         if (!ansi)
16091             line++;
16092         if (src[i] == '\n')
16093             line = 0;
16094         if (src[i] == 'm')
16095             ansi = 0;
16096     }
16097     if (dest && appData.debugMode)
16098     {
16099         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16100             count, width, line, len, *lp);
16101         show_bytes(debugFP, src, count);
16102         fprintf(debugFP, "\ndest: ");
16103         show_bytes(debugFP, dest, len);
16104         fprintf(debugFP, "\n");
16105     }
16106     *lp = dest ? line : old_line;
16107
16108     return len;
16109 }
16110
16111 // [HGM] vari: routines for shelving variations
16112
16113 void
16114 PushInner(int firstMove, int lastMove)
16115 {
16116         int i, j, nrMoves = lastMove - firstMove;
16117
16118         // push current tail of game on stack
16119         savedResult[storedGames] = gameInfo.result;
16120         savedDetails[storedGames] = gameInfo.resultDetails;
16121         gameInfo.resultDetails = NULL;
16122         savedFirst[storedGames] = firstMove;
16123         savedLast [storedGames] = lastMove;
16124         savedFramePtr[storedGames] = framePtr;
16125         framePtr -= nrMoves; // reserve space for the boards
16126         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16127             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16128             for(j=0; j<MOVE_LEN; j++)
16129                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16130             for(j=0; j<2*MOVE_LEN; j++)
16131                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16132             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16133             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16134             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16135             pvInfoList[firstMove+i-1].depth = 0;
16136             commentList[framePtr+i] = commentList[firstMove+i];
16137             commentList[firstMove+i] = NULL;
16138         }
16139
16140         storedGames++;
16141         forwardMostMove = firstMove; // truncate game so we can start variation
16142 }
16143
16144 void
16145 PushTail(int firstMove, int lastMove)
16146 {
16147         if(appData.icsActive) { // only in local mode
16148                 forwardMostMove = currentMove; // mimic old ICS behavior
16149                 return;
16150         }
16151         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16152
16153         PushInner(firstMove, lastMove);
16154         if(storedGames == 1) GreyRevert(FALSE);
16155 }
16156
16157 void
16158 PopInner(Boolean annotate)
16159 {
16160         int i, j, nrMoves;
16161         char buf[8000], moveBuf[20];
16162
16163         storedGames--;
16164         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16165         nrMoves = savedLast[storedGames] - currentMove;
16166         if(annotate) {
16167                 int cnt = 10;
16168                 if(!WhiteOnMove(currentMove))
16169                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16170                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16171                 for(i=currentMove; i<forwardMostMove; i++) {
16172                         if(WhiteOnMove(i))
16173                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16174                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16175                         strcat(buf, moveBuf);
16176                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16177                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16178                 }
16179                 strcat(buf, ")");
16180         }
16181         for(i=1; i<=nrMoves; i++) { // copy last variation back
16182             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16183             for(j=0; j<MOVE_LEN; j++)
16184                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16185             for(j=0; j<2*MOVE_LEN; j++)
16186                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16187             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16188             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16189             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16190             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16191             commentList[currentMove+i] = commentList[framePtr+i];
16192             commentList[framePtr+i] = NULL;
16193         }
16194         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16195         framePtr = savedFramePtr[storedGames];
16196         gameInfo.result = savedResult[storedGames];
16197         if(gameInfo.resultDetails != NULL) {
16198             free(gameInfo.resultDetails);
16199       }
16200         gameInfo.resultDetails = savedDetails[storedGames];
16201         forwardMostMove = currentMove + nrMoves;
16202 }
16203
16204 Boolean
16205 PopTail(Boolean annotate)
16206 {
16207         if(appData.icsActive) return FALSE; // only in local mode
16208         if(!storedGames) return FALSE; // sanity
16209         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16210
16211         PopInner(annotate);
16212
16213         if(storedGames == 0) GreyRevert(TRUE);
16214         return TRUE;
16215 }
16216
16217 void
16218 CleanupTail()
16219 {       // remove all shelved variations
16220         int i;
16221         for(i=0; i<storedGames; i++) {
16222             if(savedDetails[i])
16223                 free(savedDetails[i]);
16224             savedDetails[i] = NULL;
16225         }
16226         for(i=framePtr; i<MAX_MOVES; i++) {
16227                 if(commentList[i]) free(commentList[i]);
16228                 commentList[i] = NULL;
16229         }
16230         framePtr = MAX_MOVES-1;
16231         storedGames = 0;
16232 }
16233
16234 void
16235 LoadVariation(int index, char *text)
16236 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16237         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16238         int level = 0, move;
16239
16240         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16241         // first find outermost bracketing variation
16242         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16243             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16244                 if(*p == '{') wait = '}'; else
16245                 if(*p == '[') wait = ']'; else
16246                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16247                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16248             }
16249             if(*p == wait) wait = NULLCHAR; // closing ]} found
16250             p++;
16251         }
16252         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16253         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16254         end[1] = NULLCHAR; // clip off comment beyond variation
16255         ToNrEvent(currentMove-1);
16256         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16257         // kludge: use ParsePV() to append variation to game
16258         move = currentMove;
16259         ParsePV(start, TRUE, TRUE);
16260         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16261         ClearPremoveHighlights();
16262         CommentPopDown();
16263         ToNrEvent(currentMove+1);
16264 }
16265